dsh-github-flow 0.1.7 → 0.2.0
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 +287 -69
- package/README.md +290 -68
- package/docs/architecture.md +71 -14
- 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 +12 -0
- package/lib/api/route-helpers.d.ts +47 -0
- package/lib/api/route-helpers.js +134 -0
- package/lib/api/routes-actions.d.ts +9 -0
- package/lib/api/routes-actions.js +115 -0
- package/lib/api/routes-lifecycle.d.ts +9 -0
- package/lib/api/routes-lifecycle.js +75 -0
- package/lib/api/routes-overview.d.ts +11 -0
- package/lib/api/routes-overview.js +227 -0
- package/lib/api/routes-proxy.d.ts +18 -0
- package/lib/api/routes-proxy.js +63 -0
- package/lib/api/routes.d.ts +17 -5
- package/lib/api/routes.js +18 -417
- package/lib/client.js +1179 -40
- package/lib/commands/gh.d.ts +2 -1
- package/lib/commands/gh.js +40 -3
- package/lib/executor.d.ts +17 -0
- package/lib/executor.js +40 -2
- package/lib/main.d.ts +12 -0
- package/lib/main.js +73 -4
- package/lib/proxy.d.ts +87 -0
- package/lib/proxy.js +275 -0
- package/lib/renderers/markdown.d.ts +24 -0
- package/lib/renderers/markdown.js +493 -0
- package/lib/tools/api.d.ts +4 -0
- package/lib/tools/api.js +14 -4
- package/lib/tools/help.d.ts +31 -0
- package/lib/tools/help.js +212 -0
- package/lib/tools/issue.d.ts +32 -1
- package/lib/tools/issue.js +94 -17
- package/lib/tools/pr.d.ts +22 -1
- package/lib/tools/pr.js +67 -15
- package/lib/tools/release.d.ts +89 -0
- package/lib/tools/release.js +219 -0
- package/lib/tools/repo.d.ts +13 -1
- package/lib/tools/repo.js +46 -14
- package/lib/tools/run.d.ts +22 -1
- package/lib/tools/run.js +74 -14
- package/lib/tools/secret.d.ts +60 -0
- package/lib/tools/secret.js +175 -0
- package/lib/tools/skill.d.ts +63 -0
- package/lib/tools/skill.js +146 -0
- package/lib/types.d.ts +57 -0
- package/lib/updater.d.ts +49 -0
- package/lib/updater.js +169 -0
- package/lib/upgrade-manager.d.ts +58 -0
- package/lib/upgrade-manager.js +309 -0
- package/lib/version.d.ts +13 -0
- package/lib/version.js +13 -0
- package/package.json +3 -2
- package/lib/client/icons.d.ts +0 -13
- package/lib/client/icons.js +0 -41
- package/lib/client/index.d.ts +0 -3
- package/lib/client/index.js +0 -36
- package/lib/client/panel.d.ts +0 -1
- package/lib/client/panel.js +0 -102
- package/lib/client/style.d.ts +0 -1
- package/lib/client/style.js +0 -555
package/lib/client.js
CHANGED
|
@@ -5,6 +5,10 @@ window.__ModuleLoader__.load({
|
|
|
5
5
|
var exports = module.exports;
|
|
6
6
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
7
|
var React = require("react");
|
|
8
|
+
// 面板样式(内联维护):DSH 客户端加载器以经典脚本语义执行本产物(build.mjs 剥除
|
|
9
|
+
// export 声明即为佐证),模块级 import 在该语境是 SyntaxError。曾尝试把 CSS 拆为
|
|
10
|
+
// 独立模块经 import 引入,导致 DSH 重启时客户端加载崩溃并触发插件批量卸载——
|
|
11
|
+
// 已回滚。样式与组件的分离改由「同文件内分区注释」维持,不再跨文件。
|
|
8
12
|
var GITHUB_PANEL_CSS = `
|
|
9
13
|
/* ==========================================================================
|
|
10
14
|
DSH GitHub Flow - 主题自适应样式系统 (Light / Dark 动态联动)
|
|
@@ -804,6 +808,265 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
804
808
|
margin-top: 10px;
|
|
805
809
|
line-height: 1.4;
|
|
806
810
|
}
|
|
811
|
+
|
|
812
|
+
/* 16. 设置弹窗 (Settings Modal):左侧分区导航 + 右侧内容区 */
|
|
813
|
+
.dsh-github-modal-overlay {
|
|
814
|
+
position: fixed;
|
|
815
|
+
top: 0;
|
|
816
|
+
left: 0;
|
|
817
|
+
right: 0;
|
|
818
|
+
bottom: 0;
|
|
819
|
+
background: rgba(0, 0, 0, 0.45);
|
|
820
|
+
display: flex;
|
|
821
|
+
align-items: center;
|
|
822
|
+
justify-content: center;
|
|
823
|
+
z-index: 99999;
|
|
824
|
+
backdrop-filter: blur(2px);
|
|
825
|
+
}
|
|
826
|
+
.dsh-github-modal-dialog {
|
|
827
|
+
width: 92%;
|
|
828
|
+
max-width: 640px;
|
|
829
|
+
max-height: 86vh;
|
|
830
|
+
display: flex;
|
|
831
|
+
flex-direction: column;
|
|
832
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
833
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
834
|
+
border-radius: 12px;
|
|
835
|
+
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
|
|
836
|
+
padding: 0;
|
|
837
|
+
box-sizing: border-box;
|
|
838
|
+
overflow: hidden;
|
|
839
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
840
|
+
font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
841
|
+
}
|
|
842
|
+
body[data-ds-dark-theme] .dsh-github-modal-dialog {
|
|
843
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
844
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
845
|
+
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.45);
|
|
846
|
+
}
|
|
847
|
+
/* 弹窗头部(标题 + 关闭) */
|
|
848
|
+
.dsh-github-modal-header {
|
|
849
|
+
display: flex;
|
|
850
|
+
justify-content: space-between;
|
|
851
|
+
align-items: center;
|
|
852
|
+
padding: 18px 22px 14px;
|
|
853
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
854
|
+
flex-shrink: 0;
|
|
855
|
+
}
|
|
856
|
+
body[data-ds-dark-theme] .dsh-github-modal-header {
|
|
857
|
+
border-bottom-color: var(--dsw-alias-border-l1, #30363d);
|
|
858
|
+
}
|
|
859
|
+
/* 分区导航 + 内容 双栏骨架:窄屏(<560px)自动折叠为顶部横向页签 */
|
|
860
|
+
.dsh-github-modal-body {
|
|
861
|
+
display: flex;
|
|
862
|
+
min-height: 0;
|
|
863
|
+
flex: 1;
|
|
864
|
+
}
|
|
865
|
+
.dsh-github-settings-nav {
|
|
866
|
+
flex-shrink: 0;
|
|
867
|
+
width: 148px;
|
|
868
|
+
padding: 14px 10px;
|
|
869
|
+
border-right: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
870
|
+
display: flex;
|
|
871
|
+
flex-direction: column;
|
|
872
|
+
gap: 4px;
|
|
873
|
+
box-sizing: border-box;
|
|
874
|
+
}
|
|
875
|
+
body[data-ds-dark-theme] .dsh-github-settings-nav {
|
|
876
|
+
border-right-color: var(--dsw-alias-border-l1, #30363d);
|
|
877
|
+
}
|
|
878
|
+
.dsh-github-settings-nav-item {
|
|
879
|
+
display: flex;
|
|
880
|
+
align-items: center;
|
|
881
|
+
gap: 8px;
|
|
882
|
+
padding: 8px 10px;
|
|
883
|
+
border-radius: 6px;
|
|
884
|
+
border: none;
|
|
885
|
+
background: none;
|
|
886
|
+
cursor: pointer;
|
|
887
|
+
font-size: 13px;
|
|
888
|
+
text-align: left;
|
|
889
|
+
color: var(--dsw-alias-label-secondary, #656d76);
|
|
890
|
+
transition: background 0.12s ease, color 0.12s ease;
|
|
891
|
+
}
|
|
892
|
+
.dsh-github-settings-nav-item:hover {
|
|
893
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
894
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
895
|
+
}
|
|
896
|
+
.dsh-github-settings-nav-item.active {
|
|
897
|
+
background: rgba(9, 105, 218, 0.1);
|
|
898
|
+
color: var(--dsw-alias-link, #0969da);
|
|
899
|
+
font-weight: 600;
|
|
900
|
+
}
|
|
901
|
+
body[data-ds-dark-theme] .dsh-github-settings-nav-item:hover {
|
|
902
|
+
background: rgba(240, 246, 252, 0.06);
|
|
903
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
904
|
+
}
|
|
905
|
+
body[data-ds-dark-theme] .dsh-github-settings-nav-item.active {
|
|
906
|
+
background: rgba(88, 166, 255, 0.15);
|
|
907
|
+
color: #58a6ff;
|
|
908
|
+
}
|
|
909
|
+
.dsh-github-settings-content {
|
|
910
|
+
flex: 1;
|
|
911
|
+
min-width: 0;
|
|
912
|
+
padding: 18px 22px 20px;
|
|
913
|
+
overflow-y: auto;
|
|
914
|
+
box-sizing: border-box;
|
|
915
|
+
}
|
|
916
|
+
@media (max-width: 560px) {
|
|
917
|
+
.dsh-github-modal-body {
|
|
918
|
+
flex-direction: column;
|
|
919
|
+
}
|
|
920
|
+
.dsh-github-settings-nav {
|
|
921
|
+
width: 100%;
|
|
922
|
+
flex-direction: row;
|
|
923
|
+
border-right: none;
|
|
924
|
+
border-bottom: 1px solid var(--dsw-alias-border-l2, #d0d7e0);
|
|
925
|
+
overflow-x: auto;
|
|
926
|
+
}
|
|
927
|
+
body[data-ds-dark-theme] .dsh-github-settings-nav {
|
|
928
|
+
border-bottom-color: var(--dsw-alias-border-l1, #30363d);
|
|
929
|
+
}
|
|
930
|
+
.dsh-github-settings-nav-item {
|
|
931
|
+
white-space: nowrap;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
/* 弹窗底部操作条 */
|
|
935
|
+
.dsh-github-modal-footer {
|
|
936
|
+
display: flex;
|
|
937
|
+
justify-content: flex-end;
|
|
938
|
+
gap: 8px;
|
|
939
|
+
padding: 14px 22px;
|
|
940
|
+
border-top: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
941
|
+
flex-shrink: 0;
|
|
942
|
+
}
|
|
943
|
+
body[data-ds-dark-theme] .dsh-github-modal-footer {
|
|
944
|
+
border-top-color: var(--dsw-alias-border-l1, #30363d);
|
|
945
|
+
}
|
|
946
|
+
.dsh-github-radio-card {
|
|
947
|
+
display: flex;
|
|
948
|
+
align-items: flex-start;
|
|
949
|
+
gap: 10px;
|
|
950
|
+
padding: 10px 12px;
|
|
951
|
+
border-radius: 8px;
|
|
952
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
953
|
+
margin-bottom: 8px;
|
|
954
|
+
cursor: pointer;
|
|
955
|
+
transition: all 0.15s ease;
|
|
956
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
957
|
+
}
|
|
958
|
+
.dsh-github-radio-card:hover {
|
|
959
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
960
|
+
}
|
|
961
|
+
.dsh-github-radio-card.active {
|
|
962
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
963
|
+
background: rgba(9, 105, 218, 0.08);
|
|
964
|
+
}
|
|
965
|
+
body[data-ds-dark-theme] .dsh-github-radio-card {
|
|
966
|
+
background: var(--dsw-alias-bg-base, #161b22);
|
|
967
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
968
|
+
}
|
|
969
|
+
body[data-ds-dark-theme] .dsh-github-radio-card.active {
|
|
970
|
+
border-color: #58a6ff;
|
|
971
|
+
background: rgba(88, 166, 255, 0.12);
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/* 下拉菜单及选项在深色/浅色模式下的精准适配 */
|
|
975
|
+
.dsh-github-select {
|
|
976
|
+
width: 100%;
|
|
977
|
+
padding: 8px 12px;
|
|
978
|
+
border-radius: 6px;
|
|
979
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
980
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
981
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
982
|
+
font-size: 13px;
|
|
983
|
+
outline: none;
|
|
984
|
+
cursor: pointer;
|
|
985
|
+
box-sizing: border-box;
|
|
986
|
+
color-scheme: light;
|
|
987
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
988
|
+
}
|
|
989
|
+
.dsh-github-select:focus {
|
|
990
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
991
|
+
box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
|
|
992
|
+
}
|
|
993
|
+
.dsh-github-select option {
|
|
994
|
+
background-color: #ffffff;
|
|
995
|
+
color: #1f2328;
|
|
996
|
+
padding: 6px 10px;
|
|
997
|
+
}
|
|
998
|
+
body[data-ds-dark-theme] .dsh-github-select {
|
|
999
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
1000
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
1001
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
1002
|
+
color-scheme: dark;
|
|
1003
|
+
}
|
|
1004
|
+
body[data-ds-dark-theme] .dsh-github-select:focus {
|
|
1005
|
+
border-color: #58a6ff;
|
|
1006
|
+
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
|
|
1007
|
+
}
|
|
1008
|
+
body[data-ds-dark-theme] .dsh-github-select option {
|
|
1009
|
+
background-color: #1e1e22;
|
|
1010
|
+
color: #f0f6fc;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
/* 代理设置弹窗的文本输入框(浅色/深色双模式,设计变量对齐 select 保持视觉一致) */
|
|
1014
|
+
.dsh-github-input {
|
|
1015
|
+
padding: 7px 12px;
|
|
1016
|
+
border-radius: 6px;
|
|
1017
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
1018
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
1019
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
1020
|
+
font-size: 13px;
|
|
1021
|
+
outline: none;
|
|
1022
|
+
box-sizing: border-box;
|
|
1023
|
+
color-scheme: light;
|
|
1024
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
1025
|
+
}
|
|
1026
|
+
.dsh-github-input:focus {
|
|
1027
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
1028
|
+
box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
|
|
1029
|
+
}
|
|
1030
|
+
.dsh-github-input::placeholder {
|
|
1031
|
+
color: var(--dsw-alias-label-tertiary, #8c959f);
|
|
1032
|
+
}
|
|
1033
|
+
body[data-ds-dark-theme] .dsh-github-input {
|
|
1034
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
1035
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
1036
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
1037
|
+
color-scheme: dark;
|
|
1038
|
+
}
|
|
1039
|
+
body[data-ds-dark-theme] .dsh-github-input:focus {
|
|
1040
|
+
border-color: #58a6ff;
|
|
1041
|
+
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/* 缓存信息展示框 */
|
|
1045
|
+
.dsh-github-cache-info-box {
|
|
1046
|
+
padding: 10px 12px;
|
|
1047
|
+
border-radius: 6px;
|
|
1048
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
1049
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
1050
|
+
margin-bottom: 20px;
|
|
1051
|
+
font-size: 12px;
|
|
1052
|
+
color: var(--dsw-alias-label-secondary, #656d76);
|
|
1053
|
+
transition: all 0.15s ease;
|
|
1054
|
+
}
|
|
1055
|
+
body[data-ds-dark-theme] .dsh-github-cache-info-box {
|
|
1056
|
+
background: var(--dsw-alias-bg-base, #161b22);
|
|
1057
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
1058
|
+
color: var(--dsw-alias-label-secondary, #8c8c8c);
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/* 单选框点强调色 */
|
|
1062
|
+
.dsh-github-radio {
|
|
1063
|
+
accent-color: var(--dsw-alias-link, #0969da);
|
|
1064
|
+
cursor: pointer;
|
|
1065
|
+
margin-top: 3px;
|
|
1066
|
+
}
|
|
1067
|
+
body[data-ds-dark-theme] .dsh-github-radio {
|
|
1068
|
+
accent-color: #58a6ff;
|
|
1069
|
+
}
|
|
807
1070
|
`;
|
|
808
1071
|
function injectStyles() {
|
|
809
1072
|
var styleId = "dsh-plugin-github-styles";
|
|
@@ -949,7 +1212,25 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
949
1212
|
}, "🌐 官网安装包 (.msi/.pkg)")
|
|
950
1213
|
: null, h("button", {
|
|
951
1214
|
className: "dsh-github-action-btn",
|
|
952
|
-
onClick:
|
|
1215
|
+
onClick: function () {
|
|
1216
|
+
setToastMsg("正在快速检测登录状态...");
|
|
1217
|
+
fetch("/api/github/auth?force=true")
|
|
1218
|
+
.then(function (res) { return res.json(); })
|
|
1219
|
+
.then(function (json) {
|
|
1220
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
1221
|
+
setToastMsg("✅ 已识别到 GitHub CLI 登录态,正在拉取数据...");
|
|
1222
|
+
clearStoredGlobalOverview();
|
|
1223
|
+
onRefresh();
|
|
1224
|
+
}
|
|
1225
|
+
else {
|
|
1226
|
+
setToastMsg("⚠️ 暂未检测到登录凭据,请在终端执行 gh auth login 完成授权");
|
|
1227
|
+
onRefresh();
|
|
1228
|
+
}
|
|
1229
|
+
})
|
|
1230
|
+
.catch(function () {
|
|
1231
|
+
onRefresh();
|
|
1232
|
+
});
|
|
1233
|
+
},
|
|
953
1234
|
title: "重新检测登录与安装状态"
|
|
954
1235
|
}, "🔄 刷新状态")), toastMsg
|
|
955
1236
|
? h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-link, #0969da)", marginTop: "8px" } }, `ℹ️ ${toastMsg}`)
|
|
@@ -957,14 +1238,254 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
957
1238
|
? "💡 提示:使用包管理器安装完成后,若未立即识别,可重启 DeepSeek Harness 应用刷新系统环境变量 PATH。"
|
|
958
1239
|
: "💡 提示:凭证由官方 GitHub CLI 加密保存在操作系统安全凭据库中,授权完毕切回本窗口将自动识别刷新。"));
|
|
959
1240
|
}
|
|
1241
|
+
// 通用升级通知与一键升级卡片组件(自适应右侧栏紧凑模式与全景大面板模式)
|
|
1242
|
+
function UpgradeNotificationCard(props) {
|
|
1243
|
+
var updateInfo = props.updateInfo;
|
|
1244
|
+
var isRightbar = Boolean(props.isRightbar);
|
|
1245
|
+
if (!updateInfo || !updateInfo.hasUpdate)
|
|
1246
|
+
return null;
|
|
1247
|
+
var storageKey = "dsh_dismissed_update_" + (updateInfo.latestVersion || "latest");
|
|
1248
|
+
var [dismissed, setDismissed] = React.useState(function () {
|
|
1249
|
+
try {
|
|
1250
|
+
return localStorage.getItem(storageKey) === "true";
|
|
1251
|
+
}
|
|
1252
|
+
catch (e) {
|
|
1253
|
+
return false;
|
|
1254
|
+
}
|
|
1255
|
+
});
|
|
1256
|
+
var [status, setStatus] = React.useState("idle"); // 'idle' | 'upgrading' | 'success' | 'failed'
|
|
1257
|
+
var [msg, setMsg] = React.useState("");
|
|
1258
|
+
// 窗口切回或切 Tab 重挂载时,主动对齐后端实际的升级状态,防止状态丢失
|
|
1259
|
+
React.useEffect(function () {
|
|
1260
|
+
fetch("/api/github/upgrade-status")
|
|
1261
|
+
.then(function (res) { return res.json(); })
|
|
1262
|
+
.then(function (json) {
|
|
1263
|
+
if (json && json.ok && json.data) {
|
|
1264
|
+
if (json.data.state === "upgrading") {
|
|
1265
|
+
setStatus("upgrading");
|
|
1266
|
+
setMsg("正在拉取最新版本(约需 5~15 秒)...");
|
|
1267
|
+
}
|
|
1268
|
+
else if (json.data.state === "success") {
|
|
1269
|
+
setStatus("success");
|
|
1270
|
+
setMsg(json.data.message || "升级成功!已安装最新依赖。");
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
})
|
|
1274
|
+
.catch(function () { });
|
|
1275
|
+
}, []);
|
|
1276
|
+
if (dismissed)
|
|
1277
|
+
return null;
|
|
1278
|
+
var handleUpgrade = async function () {
|
|
1279
|
+
if (status === "upgrading")
|
|
1280
|
+
return;
|
|
1281
|
+
setStatus("upgrading");
|
|
1282
|
+
setMsg("正在拉取最新版本(约需 5~15 秒)...");
|
|
1283
|
+
try {
|
|
1284
|
+
var res = await fetch("/api/github/upgrade", { method: "POST" });
|
|
1285
|
+
var json = await res.json().catch(function () { return {}; });
|
|
1286
|
+
if (res.ok && json.ok) {
|
|
1287
|
+
setStatus("success");
|
|
1288
|
+
setMsg(json.message || "升级成功!已安装最新依赖。");
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
setStatus("failed");
|
|
1292
|
+
setMsg(json.message || "升级命令执行失败,请在宿主终端手动执行。");
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
catch (err) {
|
|
1296
|
+
setStatus("failed");
|
|
1297
|
+
setMsg("无法连接后端执行升级: " + (err.message || err));
|
|
1298
|
+
}
|
|
1299
|
+
};
|
|
1300
|
+
var handleDismiss = function () {
|
|
1301
|
+
try {
|
|
1302
|
+
localStorage.setItem(storageKey, "true");
|
|
1303
|
+
}
|
|
1304
|
+
catch (e) { }
|
|
1305
|
+
setDismissed(true);
|
|
1306
|
+
};
|
|
1307
|
+
var handleCopy = function () {
|
|
1308
|
+
var cmd = updateInfo.upgradeCommand || "pnpm dsh plugin add dsh-github-flow@latest";
|
|
1309
|
+
if (navigator.clipboard) {
|
|
1310
|
+
navigator.clipboard.writeText(cmd);
|
|
1311
|
+
alert("已复制升级命令到剪贴板:\n" + cmd);
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
prompt("请复制升级命令:", cmd);
|
|
1315
|
+
}
|
|
1316
|
+
};
|
|
1317
|
+
var handleReload = function () {
|
|
1318
|
+
window.location.reload();
|
|
1319
|
+
};
|
|
1320
|
+
var h = React.createElement;
|
|
1321
|
+
// 1. 右侧栏紧凑型模式
|
|
1322
|
+
if (isRightbar) {
|
|
1323
|
+
return h("div", {
|
|
1324
|
+
className: "dsh-github-upgrade-strip",
|
|
1325
|
+
style: {
|
|
1326
|
+
padding: "10px 12px",
|
|
1327
|
+
marginBottom: "12px",
|
|
1328
|
+
borderRadius: "8px",
|
|
1329
|
+
background: status === "success"
|
|
1330
|
+
? "var(--dsw-alias-state-success-subtle, rgba(46, 160, 67, 0.12))"
|
|
1331
|
+
: "var(--dsw-alias-bg-module-platform, rgba(9, 105, 218, 0.08))",
|
|
1332
|
+
border: "1px solid " + (status === "success"
|
|
1333
|
+
? "var(--dsw-alias-state-success-border, rgba(46, 160, 67, 0.35))"
|
|
1334
|
+
: "var(--dsw-alias-border-l2, rgba(9, 105, 218, 0.25))"),
|
|
1335
|
+
fontSize: "12px",
|
|
1336
|
+
},
|
|
1337
|
+
}, 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", {
|
|
1338
|
+
onClick: handleDismiss,
|
|
1339
|
+
title: "不再提醒此版本",
|
|
1340
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "14px", padding: 0 },
|
|
1341
|
+
}, "✕")), h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "8px", lineHeight: "1.4" } }, msg || (status === "success"
|
|
1342
|
+
? "需重启 DSH 服务彻底生效后端工具,或刷新网页预览新界面。"
|
|
1343
|
+
: "当前: v" + updateInfo.currentVersion + ",推荐升级以获取最新功能。")), h("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" } }, status === "success"
|
|
1344
|
+
? h("button", {
|
|
1345
|
+
className: "dsh-github-btn-sm",
|
|
1346
|
+
onClick: handleReload,
|
|
1347
|
+
style: { flex: 1, justifyContent: "center", background: "#2ea043", color: "#fff", borderColor: "#2ea043" },
|
|
1348
|
+
}, "🔄 刷新页面")
|
|
1349
|
+
: h("button", {
|
|
1350
|
+
className: "dsh-github-btn-sm",
|
|
1351
|
+
disabled: status === "upgrading",
|
|
1352
|
+
onClick: handleUpgrade,
|
|
1353
|
+
style: {
|
|
1354
|
+
flex: 1,
|
|
1355
|
+
justifyContent: "center",
|
|
1356
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1357
|
+
color: "#fff",
|
|
1358
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1359
|
+
opacity: status === "upgrading" ? 0.7 : 1,
|
|
1360
|
+
},
|
|
1361
|
+
}, status === "upgrading" ? "升级中..." : "⚡ 一键升级"), h("button", {
|
|
1362
|
+
className: "dsh-github-btn-sm",
|
|
1363
|
+
onClick: handleCopy,
|
|
1364
|
+
style: { fontSize: "11px", padding: "4px 8px" },
|
|
1365
|
+
}, "📋 复制命令")));
|
|
1366
|
+
}
|
|
1367
|
+
// 2. 全景驾驶舱大面板横幅模式
|
|
1368
|
+
return h("div", {
|
|
1369
|
+
className: "dsh-github-upgrade-banner",
|
|
1370
|
+
style: {
|
|
1371
|
+
display: "flex",
|
|
1372
|
+
alignItems: "center",
|
|
1373
|
+
justifyContent: "space-between",
|
|
1374
|
+
padding: "12px 18px",
|
|
1375
|
+
marginBottom: "16px",
|
|
1376
|
+
borderRadius: "8px",
|
|
1377
|
+
background: status === "success"
|
|
1378
|
+
? "linear-gradient(90deg, rgba(46, 160, 67, 0.15) 0%, rgba(9, 105, 218, 0.12) 100%)"
|
|
1379
|
+
: "linear-gradient(90deg, rgba(9, 105, 218, 0.12) 0%, rgba(137, 87, 229, 0.12) 100%)",
|
|
1380
|
+
border: "1px solid " + (status === "success" ? "rgba(46, 160, 67, 0.35)" : "rgba(9, 105, 218, 0.3)"),
|
|
1381
|
+
fontSize: "13px",
|
|
1382
|
+
},
|
|
1383
|
+
}, 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"
|
|
1384
|
+
? "升级完成!新版本 v" + updateInfo.latestVersion + " 已写入"
|
|
1385
|
+
: "发现 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"
|
|
1386
|
+
? "💡 提示:DSH 后端驻留了旧版内存,需重启 DSH 服务彻底生效后端工具;若仅预览界面改动可先刷新网页。"
|
|
1387
|
+
: "包含最新 GitHub 工具特性与性能优化,点击右侧可一键自动升级。")))), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 } }, status === "success"
|
|
1388
|
+
? h("button", {
|
|
1389
|
+
className: "dsh-github-btn-sm",
|
|
1390
|
+
onClick: handleReload,
|
|
1391
|
+
style: { background: "#2ea043", color: "#fff", borderColor: "#2ea043", padding: "6px 14px", fontWeight: 600 },
|
|
1392
|
+
}, "🔄 立即刷新页面")
|
|
1393
|
+
: h("button", {
|
|
1394
|
+
className: "dsh-github-btn-sm",
|
|
1395
|
+
disabled: status === "upgrading",
|
|
1396
|
+
onClick: handleUpgrade,
|
|
1397
|
+
style: {
|
|
1398
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1399
|
+
color: "#fff",
|
|
1400
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1401
|
+
padding: "6px 14px",
|
|
1402
|
+
fontWeight: 600,
|
|
1403
|
+
opacity: status === "upgrading" ? 0.7 : 1,
|
|
1404
|
+
},
|
|
1405
|
+
}, status === "upgrading" ? "正在自动升级中..." : "⚡ 一键自动升级"), h("button", {
|
|
1406
|
+
className: "dsh-github-btn-sm",
|
|
1407
|
+
onClick: handleCopy,
|
|
1408
|
+
style: { padding: "6px 12px" },
|
|
1409
|
+
}, "📋 复制命令"), h("button", {
|
|
1410
|
+
onClick: handleDismiss,
|
|
1411
|
+
title: "不再提醒此版本",
|
|
1412
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "16px", padding: "0 4px" },
|
|
1413
|
+
}, "✕")));
|
|
1414
|
+
}
|
|
1415
|
+
// 数据新鲜度与同步状态微标组件 (自适应全景大面板与右侧栏紧凑模式)
|
|
1416
|
+
function SyncStatusBadge(props) {
|
|
1417
|
+
var syncStatus = props.syncStatus;
|
|
1418
|
+
var lastSyncedAt = props.lastSyncedAt;
|
|
1419
|
+
var isRightbar = Boolean(props.isRightbar);
|
|
1420
|
+
var text = "";
|
|
1421
|
+
var bg = "rgba(101, 109, 118, 0.1)";
|
|
1422
|
+
var color = "var(--dsw-alias-label-secondary, #656d76)";
|
|
1423
|
+
var formatTimeAgo = function (ts) {
|
|
1424
|
+
if (!ts)
|
|
1425
|
+
return "刚刚";
|
|
1426
|
+
var diffSec = Math.floor((Date.now() - ts) / 1000);
|
|
1427
|
+
if (diffSec < 60)
|
|
1428
|
+
return "刚刚";
|
|
1429
|
+
var diffMin = Math.floor(diffSec / 60);
|
|
1430
|
+
if (diffMin < 60)
|
|
1431
|
+
return diffMin + "分钟前";
|
|
1432
|
+
var diffHour = Math.floor(diffMin / 60);
|
|
1433
|
+
if (diffHour < 24)
|
|
1434
|
+
return diffHour + "小时前";
|
|
1435
|
+
var diffDay = Math.floor(diffHour / 24);
|
|
1436
|
+
return diffDay + "天前";
|
|
1437
|
+
};
|
|
1438
|
+
if (syncStatus === "syncing") {
|
|
1439
|
+
text = isRightbar ? "⏳ 同步中..." : "⏳ 后台同步中...";
|
|
1440
|
+
bg = "rgba(9, 105, 218, 0.12)";
|
|
1441
|
+
color = "var(--dsw-alias-link, #0969da)";
|
|
1442
|
+
}
|
|
1443
|
+
else if (syncStatus === "fresh") {
|
|
1444
|
+
var timeStr = lastSyncedAt ? new Date(lastSyncedAt).toLocaleTimeString() : "";
|
|
1445
|
+
text = "✅ " + (isRightbar ? "最新" : "已是最新") + (timeStr ? " (" + timeStr + ")" : "");
|
|
1446
|
+
bg = "rgba(46, 160, 67, 0.12)";
|
|
1447
|
+
color = "var(--dsw-alias-state-success, #2ea043)";
|
|
1448
|
+
}
|
|
1449
|
+
else if (syncStatus === "cached") {
|
|
1450
|
+
text = "📦 本地快照 (" + formatTimeAgo(lastSyncedAt) + ")";
|
|
1451
|
+
bg = "rgba(101, 109, 118, 0.12)";
|
|
1452
|
+
color = "var(--dsw-alias-label-secondary, #656d76)";
|
|
1453
|
+
}
|
|
1454
|
+
else if (syncStatus === "failed") {
|
|
1455
|
+
text = isRightbar ? "⚠️ 同步超时" : "⚠️ 同步超时,已保留本地缓存";
|
|
1456
|
+
bg = "rgba(207, 34, 46, 0.1)";
|
|
1457
|
+
color = "var(--dsw-alias-state-error-primary, #cf222e)";
|
|
1458
|
+
}
|
|
1459
|
+
if (!text)
|
|
1460
|
+
return null;
|
|
1461
|
+
return React.createElement("span", {
|
|
1462
|
+
className: "dsh-github-sync-badge",
|
|
1463
|
+
style: {
|
|
1464
|
+
fontSize: isRightbar ? "10px" : "11px",
|
|
1465
|
+
padding: isRightbar ? "2px 6px" : "3px 8px",
|
|
1466
|
+
borderRadius: "12px",
|
|
1467
|
+
background: bg,
|
|
1468
|
+
color: color,
|
|
1469
|
+
marginLeft: isRightbar ? "6px" : "10px",
|
|
1470
|
+
fontWeight: 500,
|
|
1471
|
+
display: "inline-flex",
|
|
1472
|
+
alignItems: "center",
|
|
1473
|
+
whiteSpace: "nowrap",
|
|
1474
|
+
},
|
|
1475
|
+
}, text);
|
|
1476
|
+
}
|
|
960
1477
|
// 渲染 GitHub 仓库信息与操作视图的核心函数
|
|
961
|
-
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false) {
|
|
1478
|
+
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0) {
|
|
962
1479
|
var auth = data ? data.auth : null;
|
|
963
1480
|
var repo = data ? data.repo : null;
|
|
964
1481
|
var prs = (data && data.pullRequests) || [];
|
|
965
1482
|
var issues = (data && data.issues) || [];
|
|
966
1483
|
var runs = (data && data.runs) || [];
|
|
967
|
-
var
|
|
1484
|
+
var effectiveUpdateInfo = updateInfo || data?.updateInfo;
|
|
1485
|
+
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
1486
|
+
? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar })
|
|
1487
|
+
: 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", {
|
|
968
1489
|
className: "dsh-github-refresh-btn",
|
|
969
1490
|
onClick: onRefresh,
|
|
970
1491
|
disabled: loading,
|
|
@@ -1075,23 +1596,212 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1075
1596
|
var authGuideEl = !auth?.loggedIn
|
|
1076
1597
|
? h(AuthGuideCard, { auth, onRefresh, isRightbar })
|
|
1077
1598
|
: null;
|
|
1078
|
-
return [headerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, issueSectionEl, runSectionEl].filter(Boolean);
|
|
1599
|
+
return [headerEl, upgradeBannerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, issueSectionEl, runSectionEl].filter(Boolean);
|
|
1600
|
+
}
|
|
1601
|
+
// 全局持久化缓存与偏好配置键名
|
|
1602
|
+
var GLOBAL_OVERVIEW_CACHE_KEY = "dsh_github_global_overview_snapshot_v1";
|
|
1603
|
+
var SYNC_SETTINGS_STORAGE_KEY = "dsh_github_sync_settings_v1";
|
|
1604
|
+
// 同步策略收敛为两态:'swr'(打开秒开旧数据+后台静默拉新)| 'manual'(仅手动刷新)。
|
|
1605
|
+
// 旧的第三态 'interval'(前端 setInterval 轮询)已删除:浏览器对后台标签页节流
|
|
1606
|
+
// 定时器、组件卸载即销毁,该模式在真实环境几乎从不按承诺工作(停留短于间隔则
|
|
1607
|
+
// 永不触发);其「打开即新鲜」的诉求由 SWR 挂载自愈覆盖,「持续同步」点刷新即可。
|
|
1608
|
+
var DEFAULT_SYNC_SETTINGS = {
|
|
1609
|
+
strategy: "swr", // 'swr' | 'manual'
|
|
1610
|
+
};
|
|
1611
|
+
function getLocalSyncSettings() {
|
|
1612
|
+
try {
|
|
1613
|
+
var raw = localStorage.getItem(SYNC_SETTINGS_STORAGE_KEY);
|
|
1614
|
+
if (!raw)
|
|
1615
|
+
return DEFAULT_SYNC_SETTINGS;
|
|
1616
|
+
var parsed = JSON.parse(raw);
|
|
1617
|
+
var merged = Object.assign({}, DEFAULT_SYNC_SETTINGS, parsed);
|
|
1618
|
+
// 旧版本迁移:曾选过 'interval' 的用户自动回落 SWR(最接近原意图),
|
|
1619
|
+
// 并立即覆写存储防止每次读取都走迁移分支。
|
|
1620
|
+
if (merged.strategy === "interval") {
|
|
1621
|
+
merged.strategy = "swr";
|
|
1622
|
+
saveLocalSyncSettings(merged);
|
|
1623
|
+
}
|
|
1624
|
+
return merged;
|
|
1625
|
+
}
|
|
1626
|
+
catch (e) {
|
|
1627
|
+
return DEFAULT_SYNC_SETTINGS;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
function saveLocalSyncSettings(settings) {
|
|
1631
|
+
try {
|
|
1632
|
+
localStorage.setItem(SYNC_SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
|
1633
|
+
// 跨组件事件通知:确保主页设置被修改后,右侧栏及所有实例即时同步新规则
|
|
1634
|
+
if (typeof window !== "undefined") {
|
|
1635
|
+
window.dispatchEvent(new CustomEvent("dsh-github-sync-settings-change", { detail: settings }));
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
catch (e) { }
|
|
1639
|
+
}
|
|
1640
|
+
function getStoredGlobalOverview() {
|
|
1641
|
+
try {
|
|
1642
|
+
var raw = localStorage.getItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1643
|
+
if (!raw)
|
|
1644
|
+
return null;
|
|
1645
|
+
var parsed = JSON.parse(raw);
|
|
1646
|
+
// 毒丸缓存自愈清洗:如果缓存中的快照显示「未登录」或未就绪,立即将其从 localStorage 彻底抹除!
|
|
1647
|
+
if (!parsed || !parsed.data || !parsed.data.auth || !parsed.data.auth.loggedIn) {
|
|
1648
|
+
localStorage.removeItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1649
|
+
return null;
|
|
1650
|
+
}
|
|
1651
|
+
if (typeof parsed.savedAt === "number") {
|
|
1652
|
+
return parsed;
|
|
1653
|
+
}
|
|
1654
|
+
return null;
|
|
1655
|
+
}
|
|
1656
|
+
catch (e) {
|
|
1657
|
+
return null;
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
function saveStoredGlobalOverview(data) {
|
|
1661
|
+
// 毒丸拦截守卫:绝不持久化存储未登录状态、空数据或异常快照!
|
|
1662
|
+
if (!data || !data.auth || !data.auth.loggedIn) {
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
try {
|
|
1666
|
+
localStorage.setItem(GLOBAL_OVERVIEW_CACHE_KEY, JSON.stringify({
|
|
1667
|
+
data: data,
|
|
1668
|
+
savedAt: Date.now(),
|
|
1669
|
+
}));
|
|
1670
|
+
}
|
|
1671
|
+
catch (e) { }
|
|
1672
|
+
}
|
|
1673
|
+
function clearStoredGlobalOverview() {
|
|
1674
|
+
try {
|
|
1675
|
+
localStorage.removeItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1676
|
+
// 彻底清空所有右侧栏工作区历史快照,确保全局一致
|
|
1677
|
+
if (typeof localStorage !== "undefined") {
|
|
1678
|
+
for (var i = localStorage.length - 1; i >= 0; i--) {
|
|
1679
|
+
var k = localStorage.key(i);
|
|
1680
|
+
if (k && k.indexOf("dsh_github_rightbar_snapshot_") === 0) {
|
|
1681
|
+
localStorage.removeItem(k);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
if (typeof window !== "undefined") {
|
|
1686
|
+
window.dispatchEvent(new CustomEvent("dsh-github-cache-cleared"));
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
catch (e) { }
|
|
1690
|
+
}
|
|
1691
|
+
function getCacheStorageMeta() {
|
|
1692
|
+
try {
|
|
1693
|
+
var raw = localStorage.getItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1694
|
+
if (!raw)
|
|
1695
|
+
return { exists: false, sizeKb: 0, savedAt: 0 };
|
|
1696
|
+
var parsed = JSON.parse(raw);
|
|
1697
|
+
var sizeKb = Math.round((raw.length * 2) / 1024 * 10) / 10;
|
|
1698
|
+
return { exists: true, sizeKb: sizeKb, savedAt: parsed?.savedAt || 0 };
|
|
1699
|
+
}
|
|
1700
|
+
catch (e) {
|
|
1701
|
+
return { exists: false, sizeKb: 0, savedAt: 0 };
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
// 右侧栏专属本地持久化快照工具
|
|
1705
|
+
function getRightbarStorageKey(cwd) {
|
|
1706
|
+
var safe = (cwd || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1707
|
+
return "dsh_github_rightbar_snapshot_" + safe;
|
|
1708
|
+
}
|
|
1709
|
+
function getStoredRightbarSnapshot(cwd) {
|
|
1710
|
+
try {
|
|
1711
|
+
var key = getRightbarStorageKey(cwd);
|
|
1712
|
+
var raw = localStorage.getItem(key);
|
|
1713
|
+
if (!raw)
|
|
1714
|
+
return null;
|
|
1715
|
+
var parsed = JSON.parse(raw);
|
|
1716
|
+
// 毒丸拦截:若快照未登录,物理删除
|
|
1717
|
+
if (!parsed || !parsed.data || !parsed.data.auth || !parsed.data.auth.loggedIn) {
|
|
1718
|
+
localStorage.removeItem(key);
|
|
1719
|
+
return null;
|
|
1720
|
+
}
|
|
1721
|
+
return { data: parsed.data, savedAt: typeof parsed.savedAt === "number" ? parsed.savedAt : 0 };
|
|
1722
|
+
}
|
|
1723
|
+
catch (e) {
|
|
1724
|
+
return null;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
function saveStoredRightbarData(cwd, data) {
|
|
1728
|
+
if (!data || !data.auth || !data.auth.loggedIn)
|
|
1729
|
+
return;
|
|
1730
|
+
try {
|
|
1731
|
+
var key = getRightbarStorageKey(cwd);
|
|
1732
|
+
localStorage.setItem(key, JSON.stringify({
|
|
1733
|
+
data: data,
|
|
1734
|
+
savedAt: Date.now(),
|
|
1735
|
+
}));
|
|
1736
|
+
}
|
|
1737
|
+
catch (e) { }
|
|
1079
1738
|
}
|
|
1080
1739
|
// 1. 右侧栏专属视图 (Right Sidebar Tab Body)
|
|
1081
1740
|
function GithubRightbarBody(props) {
|
|
1082
|
-
var
|
|
1083
|
-
var [data, setData] = React.useState(null);
|
|
1084
|
-
var [error, setError] = React.useState(null);
|
|
1085
|
-
// 核心:直接从会话 Props 拿到真实物理工作区路径!
|
|
1741
|
+
var sessionId = props ? props.sessionId : null;
|
|
1086
1742
|
var cwd = null;
|
|
1087
|
-
if (props && props.useSessions &&
|
|
1743
|
+
if (props && typeof props.useSessions === "function" && sessionId) {
|
|
1088
1744
|
try {
|
|
1089
|
-
cwd = props.useSessions((sessions) => sessions?.byId?.[
|
|
1745
|
+
cwd = props.useSessions((sessions) => sessions?.byId?.[sessionId]?.cwd) || null;
|
|
1090
1746
|
}
|
|
1091
1747
|
catch (e) { }
|
|
1092
1748
|
}
|
|
1749
|
+
// 核心原则:右侧栏与全景主页使用同一套在“主页-设置”中配置的缓存与刷新规则事实源
|
|
1750
|
+
var initialSettings = React.useMemo(() => getLocalSyncSettings(), []);
|
|
1751
|
+
var [syncSettings, setSyncSettings] = React.useState(initialSettings);
|
|
1752
|
+
// SWR 秒开机制:初始数据直接从本地快照读取,0ms 瞬间展示!
|
|
1753
|
+
var initialSnapshot = React.useMemo(() => getStoredRightbarSnapshot(cwd), [cwd]);
|
|
1754
|
+
var [data, setData] = React.useState(initialSnapshot?.data || null);
|
|
1755
|
+
var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
|
|
1756
|
+
var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
|
|
1757
|
+
var [loading, setLoading] = React.useState(false);
|
|
1758
|
+
var [error, setError] = React.useState(null);
|
|
1759
|
+
var [updateInfo, setUpdateInfo] = React.useState(null);
|
|
1760
|
+
// 跨组件事件监听:当主页修改了同步设置或清空缓存时,右侧栏即时无感对齐
|
|
1761
|
+
React.useEffect(function () {
|
|
1762
|
+
var onSettingsChange = function (e) {
|
|
1763
|
+
if (e && e.detail) {
|
|
1764
|
+
setSyncSettings(e.detail);
|
|
1765
|
+
}
|
|
1766
|
+
};
|
|
1767
|
+
var onCacheCleared = function () {
|
|
1768
|
+
setData(null);
|
|
1769
|
+
setLastSyncedAt(0);
|
|
1770
|
+
setSyncStatus("loading");
|
|
1771
|
+
};
|
|
1772
|
+
window.addEventListener("dsh-github-sync-settings-change", onSettingsChange);
|
|
1773
|
+
window.addEventListener("dsh-github-cache-cleared", onCacheCleared);
|
|
1774
|
+
return function () {
|
|
1775
|
+
window.removeEventListener("dsh-github-sync-settings-change", onSettingsChange);
|
|
1776
|
+
window.removeEventListener("dsh-github-cache-cleared", onCacheCleared);
|
|
1777
|
+
};
|
|
1778
|
+
}, []);
|
|
1779
|
+
// gh 数据拉取完毕后,闲时 1.5 秒异步检测升级,绝对不抢占主业务首屏资源
|
|
1780
|
+
React.useEffect(() => {
|
|
1781
|
+
if (!data)
|
|
1782
|
+
return;
|
|
1783
|
+
var timer = setTimeout(function () {
|
|
1784
|
+
fetch("/api/github/check-update")
|
|
1785
|
+
.then(function (res) { return res.json(); })
|
|
1786
|
+
.then(function (json) {
|
|
1787
|
+
if (json && json.ok && json.data) {
|
|
1788
|
+
setUpdateInfo(json.data);
|
|
1789
|
+
}
|
|
1790
|
+
})
|
|
1791
|
+
.catch(function () { });
|
|
1792
|
+
}, 1500);
|
|
1793
|
+
return function () { clearTimeout(timer); };
|
|
1794
|
+
}, [data]);
|
|
1795
|
+
// 遵循全局规则的数据拉取核心函数 (仅依赖标量 cwd 与 sessionId,坚决去除 props 引用依赖,彻底终结死循环)
|
|
1093
1796
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
1094
|
-
|
|
1797
|
+
// 规则执行:若主页设置为「纯手动模式 (manual)」,且非用户点击强制刷新,且本地已有数据,坚决不打后台网络!
|
|
1798
|
+
if (!forceRefresh && syncSettings.strategy === "manual" && data) {
|
|
1799
|
+
return;
|
|
1800
|
+
}
|
|
1801
|
+
if (forceRefresh || !data) {
|
|
1802
|
+
setLoading(true);
|
|
1803
|
+
}
|
|
1804
|
+
setSyncStatus(data ? "syncing" : "loading");
|
|
1095
1805
|
setError(null);
|
|
1096
1806
|
try {
|
|
1097
1807
|
if (forceRefresh) {
|
|
@@ -1099,8 +1809,8 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1099
1809
|
}
|
|
1100
1810
|
var url = "/api/github/overview";
|
|
1101
1811
|
var params = [];
|
|
1102
|
-
if (
|
|
1103
|
-
params.push("sessionId=" + encodeURIComponent(
|
|
1812
|
+
if (sessionId) {
|
|
1813
|
+
params.push("sessionId=" + encodeURIComponent(sessionId));
|
|
1104
1814
|
}
|
|
1105
1815
|
if (cwd) {
|
|
1106
1816
|
params.push("cwd=" + encodeURIComponent(cwd));
|
|
@@ -1111,39 +1821,77 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1111
1821
|
var res = await fetch(url);
|
|
1112
1822
|
if (!res.ok) {
|
|
1113
1823
|
var errText = await res.text().catch(() => "");
|
|
1114
|
-
|
|
1824
|
+
var msg = `后端服务未就绪 (${res.status}): ${errText || res.statusText}`;
|
|
1825
|
+
if (data) {
|
|
1826
|
+
setSyncStatus("failed");
|
|
1827
|
+
}
|
|
1828
|
+
else {
|
|
1829
|
+
setError(msg);
|
|
1830
|
+
}
|
|
1115
1831
|
return;
|
|
1116
1832
|
}
|
|
1117
1833
|
var json = await res.json();
|
|
1118
1834
|
if (json.ok && json.data) {
|
|
1119
1835
|
setData(json.data);
|
|
1836
|
+
saveStoredRightbarData(cwd, json.data); // 写入持久化快照
|
|
1837
|
+
setLastSyncedAt(Date.now());
|
|
1838
|
+
setSyncStatus("fresh");
|
|
1839
|
+
if (json.data.updateInfo) {
|
|
1840
|
+
setUpdateInfo(json.data.updateInfo);
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
else if (data) {
|
|
1844
|
+
setSyncStatus("failed");
|
|
1120
1845
|
}
|
|
1121
1846
|
else {
|
|
1122
1847
|
setError(json.error || "获取状态失败");
|
|
1123
1848
|
}
|
|
1124
1849
|
}
|
|
1125
1850
|
catch (err) {
|
|
1126
|
-
|
|
1851
|
+
if (data) {
|
|
1852
|
+
setSyncStatus("failed");
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
setError(err.message || "网络异常");
|
|
1856
|
+
}
|
|
1127
1857
|
}
|
|
1128
1858
|
finally {
|
|
1129
1859
|
setLoading(false);
|
|
1130
1860
|
}
|
|
1131
|
-
}, [cwd,
|
|
1861
|
+
}, [cwd, sessionId, data, syncSettings.strategy]);
|
|
1862
|
+
// 挂载与策略调度:与主页规则 100% 保持一致。
|
|
1863
|
+
// SWR 分支不判 data:挂载 300ms 后台静默拉新,昨天的持久化快照当场自愈——
|
|
1864
|
+
// 「打开就能看到最新」是 SWR 的定义,不依赖用户停留时长。
|
|
1132
1865
|
React.useEffect(() => {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1866
|
+
if (syncSettings.strategy === "swr") {
|
|
1867
|
+
var timer = setTimeout(function () { loadData(false); }, 300);
|
|
1868
|
+
return function () { clearTimeout(timer); };
|
|
1869
|
+
}
|
|
1870
|
+
else if (!data) {
|
|
1871
|
+
loadData(false);
|
|
1872
|
+
}
|
|
1873
|
+
}, [cwd, sessionId, syncSettings.strategy]);
|
|
1874
|
+
// (原定时轮询调度已随 'interval' 策略一并移除:前端 setInterval 受浏览器
|
|
1875
|
+
// 后台节流且随组件卸载销毁,无法兑现「定时自动更新」承诺)
|
|
1876
|
+
// 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
|
|
1136
1877
|
React.useEffect(() => {
|
|
1137
1878
|
var onFocus = () => {
|
|
1138
1879
|
if (!data?.auth?.loggedIn) {
|
|
1139
|
-
|
|
1880
|
+
fetch("/api/github/auth?force=true")
|
|
1881
|
+
.then(function (res) { return res.json(); })
|
|
1882
|
+
.then(function (json) {
|
|
1883
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
1884
|
+
loadData(true);
|
|
1885
|
+
}
|
|
1886
|
+
})
|
|
1887
|
+
.catch(function () { });
|
|
1140
1888
|
}
|
|
1141
1889
|
};
|
|
1142
1890
|
window.addEventListener("focus", onFocus);
|
|
1143
1891
|
return () => window.removeEventListener("focus", onFocus);
|
|
1144
1892
|
}, [data, loadData]);
|
|
1145
1893
|
var h = React.createElement;
|
|
1146
|
-
return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true));
|
|
1894
|
+
return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt));
|
|
1147
1895
|
}
|
|
1148
1896
|
// 2. 右侧栏 Tab 标题组件
|
|
1149
1897
|
function GithubRightbarTitle() {
|
|
@@ -1164,76 +1912,467 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1164
1912
|
className: "dsh-github-icon-inner" + (active ? " active" : "")
|
|
1165
1913
|
}, renderOctocat(18, "currentColor")));
|
|
1166
1914
|
}
|
|
1915
|
+
// 全景驾驶舱「⚙️ 设置」模态弹窗组件:左侧分区导航(数据同步 / 网络代理)+ 右侧内容区。
|
|
1916
|
+
// 为什么改多分区:弹窗承载能力从单一「同步偏好」扩展为多项插件设置(后续可继续加),
|
|
1917
|
+
// 单页滚动堆叠会让用户找不到入口;分区导航让每项设置拥有独立心智空间与保存动作。
|
|
1918
|
+
function GlobalSyncSettingsModal(props) {
|
|
1919
|
+
if (!props.isOpen)
|
|
1920
|
+
return null;
|
|
1921
|
+
// ---- 当前激活分区:sync(数据同步)/ proxy(网络代理)----
|
|
1922
|
+
var [activeSection, setActiveSection] = React.useState("sync");
|
|
1923
|
+
var [strategy, setStrategy] = React.useState(props.currentSettings.strategy || "swr");
|
|
1924
|
+
var [cacheMeta, setCacheMeta] = React.useState(getCacheStorageMeta());
|
|
1925
|
+
var [clearTip, setClearTip] = React.useState("");
|
|
1926
|
+
// ---- 网络代理分区状态 ----
|
|
1927
|
+
// 打开弹窗时从后端拉取真实代理状态(权威源是 proxy.json 而非 localStorage),
|
|
1928
|
+
// 避免多窗口/外部手改文件导致的 UI 与实际生效配置漂移。
|
|
1929
|
+
var [proxyEnabled, setProxyEnabled] = React.useState(false);
|
|
1930
|
+
var [proxyUrl, setProxyUrl] = React.useState("");
|
|
1931
|
+
var [proxyNoProxy, setProxyNoProxy] = React.useState("localhost,127.0.0.1");
|
|
1932
|
+
var [proxyApplyNpm, setProxyApplyNpm] = React.useState(true);
|
|
1933
|
+
var [proxyBusy, setProxyBusy] = React.useState(false);
|
|
1934
|
+
var [proxyMsg, setProxyMsg] = React.useState(null);
|
|
1935
|
+
var [proxyProbe, setProxyProbe] = React.useState(null);
|
|
1936
|
+
React.useEffect(function () {
|
|
1937
|
+
if (!props.isOpen)
|
|
1938
|
+
return;
|
|
1939
|
+
fetch("/api/github/proxy")
|
|
1940
|
+
.then(function (r) { return r.json(); })
|
|
1941
|
+
.then(function (j) {
|
|
1942
|
+
if (j && j.ok && j.state) {
|
|
1943
|
+
setProxyEnabled(Boolean(j.state.config && j.state.config.url));
|
|
1944
|
+
setProxyUrl((j.state.config && j.state.config.url) || "");
|
|
1945
|
+
setProxyNoProxy((j.state.config && j.state.config.noProxy) || "localhost,127.0.0.1");
|
|
1946
|
+
setProxyApplyNpm(j.state.config ? j.state.config.applyToPackageManager !== false : true);
|
|
1947
|
+
if (j.state.lastProbe)
|
|
1948
|
+
setProxyProbe(j.state.lastProbe);
|
|
1949
|
+
}
|
|
1950
|
+
})
|
|
1951
|
+
.catch(function () { });
|
|
1952
|
+
}, [props.isOpen]);
|
|
1953
|
+
// 键盘 Escape 快捷关闭支持
|
|
1954
|
+
React.useEffect(function () {
|
|
1955
|
+
var onKeyDown = function (e) {
|
|
1956
|
+
if (e.key === "Escape" || e.keyCode === 27) {
|
|
1957
|
+
props.onClose();
|
|
1958
|
+
}
|
|
1959
|
+
};
|
|
1960
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1961
|
+
return function () { window.removeEventListener("keydown", onKeyDown); };
|
|
1962
|
+
}, [props.onClose]);
|
|
1963
|
+
var h = React.createElement;
|
|
1964
|
+
var handleSave = function () {
|
|
1965
|
+
var updated = {
|
|
1966
|
+
strategy: strategy,
|
|
1967
|
+
};
|
|
1968
|
+
saveLocalSyncSettings(updated);
|
|
1969
|
+
props.onSave(updated);
|
|
1970
|
+
props.onClose();
|
|
1971
|
+
};
|
|
1972
|
+
var handleClear = function () {
|
|
1973
|
+
clearStoredGlobalOverview();
|
|
1974
|
+
props.onClearCache();
|
|
1975
|
+
setCacheMeta({ exists: false, sizeKb: 0, savedAt: 0 });
|
|
1976
|
+
setClearTip("本地缓存已彻底清空!");
|
|
1977
|
+
setTimeout(function () { setClearTip(""); }, 2000);
|
|
1978
|
+
};
|
|
1979
|
+
// 代理保存:POST 后端后立即全局生效(gh 子进程/升级链路 env + npm 探测 dispatcher)。
|
|
1980
|
+
// 保存成功即提示「无需重启」,与「设置完成之后全局生效」的语义对齐。
|
|
1981
|
+
var handleProxySave = function () {
|
|
1982
|
+
setProxyBusy(true);
|
|
1983
|
+
setProxyMsg(null);
|
|
1984
|
+
fetch("/api/github/proxy", {
|
|
1985
|
+
method: "POST",
|
|
1986
|
+
headers: { "Content-Type": "application/json" },
|
|
1987
|
+
body: JSON.stringify({
|
|
1988
|
+
url: proxyEnabled ? proxyUrl.trim() : "",
|
|
1989
|
+
noProxy: proxyNoProxy,
|
|
1990
|
+
applyToPackageManager: proxyApplyNpm,
|
|
1991
|
+
}),
|
|
1992
|
+
})
|
|
1993
|
+
.then(function (r) { return r.json(); })
|
|
1994
|
+
.then(function (j) {
|
|
1995
|
+
setProxyBusy(false);
|
|
1996
|
+
if (j && j.ok) {
|
|
1997
|
+
setProxyMsg({
|
|
1998
|
+
kind: "ok",
|
|
1999
|
+
text: j.state && j.state.config && j.state.config.url
|
|
2000
|
+
? "🌐 代理已保存并全局生效(驾驶舱 / 右侧栏 / Agent 工具 / 升级链路),无需重启"
|
|
2001
|
+
: "已切换为系统默认网络(直连或系统环境代理),无需重启",
|
|
2002
|
+
});
|
|
2003
|
+
if (j.state && j.state.lastProbe)
|
|
2004
|
+
setProxyProbe(j.state.lastProbe);
|
|
2005
|
+
}
|
|
2006
|
+
else {
|
|
2007
|
+
setProxyMsg({ kind: "err", text: (j && j.message) || "保存失败" });
|
|
2008
|
+
}
|
|
2009
|
+
})
|
|
2010
|
+
.catch(function (e) {
|
|
2011
|
+
setProxyBusy(false);
|
|
2012
|
+
setProxyMsg({ kind: "err", text: "网络请求失败: " + (e && e.message ? e.message : e) });
|
|
2013
|
+
});
|
|
2014
|
+
};
|
|
2015
|
+
// 连通性探测:验证经当前配置访问 GitHub API 的可达性(2.5s 超时由后端控制)
|
|
2016
|
+
var handleProxyProbe = function () {
|
|
2017
|
+
setProxyBusy(true);
|
|
2018
|
+
setProxyMsg(null);
|
|
2019
|
+
fetch("/api/github/proxy/probe", { method: "POST" })
|
|
2020
|
+
.then(function (r) { return r.json(); })
|
|
2021
|
+
.then(function (j) {
|
|
2022
|
+
setProxyBusy(false);
|
|
2023
|
+
if (j && j.ok && j.probe) {
|
|
2024
|
+
setProxyProbe(j.probe);
|
|
2025
|
+
setProxyMsg(j.probe.ok
|
|
2026
|
+
? { kind: "ok", text: "✅ GitHub API 可达 (" + j.probe.latencyMs + "ms)" }
|
|
2027
|
+
: { kind: "err", text: "❌ " + (j.probe.error || "探测失败") });
|
|
2028
|
+
}
|
|
2029
|
+
else {
|
|
2030
|
+
setProxyMsg({ kind: "err", text: (j && j.message) || "探测失败" });
|
|
2031
|
+
}
|
|
2032
|
+
})
|
|
2033
|
+
.catch(function (e) {
|
|
2034
|
+
setProxyBusy(false);
|
|
2035
|
+
setProxyMsg({ kind: "err", text: "探测请求失败: " + (e && e.message ? e.message : e) });
|
|
2036
|
+
});
|
|
2037
|
+
};
|
|
2038
|
+
return h("div", { className: "dsh-github-modal-overlay", onClick: props.onClose }, h("div", {
|
|
2039
|
+
className: "dsh-github-modal-dialog",
|
|
2040
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
2041
|
+
},
|
|
2042
|
+
// 弹窗头部:标题 + 关闭按钮
|
|
2043
|
+
h("div", { className: "dsh-github-modal-header" }, h("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 } }, "⚙️ 设置"), h("button", {
|
|
2044
|
+
style: { background: "none", border: "none", cursor: "pointer", fontSize: "18px", color: "var(--dsw-alias-label-secondary, #656d76)" },
|
|
2045
|
+
onClick: props.onClose,
|
|
2046
|
+
}, "✕")),
|
|
2047
|
+
// 双栏骨架:左侧分区导航 + 右侧内容区(窄屏自动折叠为横向页签)
|
|
2048
|
+
h("div", { className: "dsh-github-modal-body" }, h("div", { className: "dsh-github-settings-nav" }, h("button", {
|
|
2049
|
+
className: "dsh-github-settings-nav-item" + (activeSection === "sync" ? " active" : ""),
|
|
2050
|
+
onClick: function () { setActiveSection("sync"); },
|
|
2051
|
+
}, "🔄 数据同步"), h("button", {
|
|
2052
|
+
className: "dsh-github-settings-nav-item" + (activeSection === "proxy" ? " active" : ""),
|
|
2053
|
+
onClick: function () { setActiveSection("proxy"); },
|
|
2054
|
+
}, "🌐 网络代理")), 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
|
+
// 刷新策略选择
|
|
2056
|
+
h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "数据刷新策略"),
|
|
2057
|
+
// 策略 A: SWR 秒开
|
|
2058
|
+
h("div", {
|
|
2059
|
+
className: "dsh-github-radio-card" + (strategy === "swr" ? " active" : ""),
|
|
2060
|
+
onClick: function () { setStrategy("swr"); },
|
|
2061
|
+
}, 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秒开),后台静默同步最新状态。"))),
|
|
2062
|
+
// 策略 B: 纯手动刷新
|
|
2063
|
+
h("div", {
|
|
2064
|
+
className: "dsh-github-radio-card" + (strategy === "manual" ? " active" : ""),
|
|
2065
|
+
onClick: function () { setStrategy("manual"); },
|
|
2066
|
+
}, 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 进程,最省电、最省资源;想更新时点击「🔄 刷新」即可。")))
|
|
2067
|
+
// (第三种「定时自动轮询」策略已移除:前端定时器受浏览器后台节流且随
|
|
2068
|
+
// 组件卸载销毁,无法兑现定时更新承诺;SWR 挂载自愈已覆盖「打开即新鲜」)
|
|
2069
|
+
),
|
|
2070
|
+
// 本地缓存信息与清理
|
|
2071
|
+
h("div", {
|
|
2072
|
+
className: "dsh-github-cache-info-box",
|
|
2073
|
+
}, h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, h("span", null, cacheMeta.exists
|
|
2074
|
+
? `📦 本地缓存: 约 ${cacheMeta.sizeKb} KB (上次保存: ${new Date(cacheMeta.savedAt).toLocaleTimeString()})`
|
|
2075
|
+
: "📦 暂无本地持久化缓存"), cacheMeta.exists ? h("button", {
|
|
2076
|
+
className: "dsh-github-btn-sm",
|
|
2077
|
+
style: { padding: "3px 8px", fontSize: "11px", color: "var(--dsw-alias-state-error-primary, #cf222e)" },
|
|
2078
|
+
onClick: handleClear,
|
|
2079
|
+
}, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null)) : h("div", null,
|
|
2080
|
+
// 🌐 网络代理分区:设置保存即全局生效(驾驶舱/右侧栏/Agent 工具/升级链路),
|
|
2081
|
+
// 未启用时回落系统环境代理或直连(「默认走系统设置」语义)。
|
|
2082
|
+
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", {
|
|
2083
|
+
type: "checkbox",
|
|
2084
|
+
checked: proxyEnabled,
|
|
2085
|
+
onChange: function (e) { setProxyEnabled(Boolean(e.target.checked)); },
|
|
2086
|
+
}), "启用代理"), proxyEnabled ? h("div", null, h("div", { style: { fontSize: "12px", marginBottom: "4px" } }, "代理地址"), h("input", {
|
|
2087
|
+
className: "dsh-github-input",
|
|
2088
|
+
style: { width: "100%", boxSizing: "border-box", padding: "6px 10px", fontSize: "13px", marginBottom: "10px" },
|
|
2089
|
+
placeholder: "http://127.0.0.1:7890 或 socks5h://127.0.0.1:1080(DNS 由代理解析)",
|
|
2090
|
+
value: proxyUrl,
|
|
2091
|
+
onChange: function (e) { setProxyUrl(e.target.value); },
|
|
2092
|
+
}), h("div", { style: { fontSize: "12px", marginBottom: "4px" } }, "NO_PROXY 排除列表(逗号分隔,localhost 与 127.0.0.1 强制包含)"), h("input", {
|
|
2093
|
+
className: "dsh-github-input",
|
|
2094
|
+
style: { width: "100%", boxSizing: "border-box", padding: "6px 10px", fontSize: "12px", marginBottom: "10px" },
|
|
2095
|
+
value: proxyNoProxy,
|
|
2096
|
+
onChange: function (e) { setProxyNoProxy(e.target.value); },
|
|
2097
|
+
}), h("label", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "8px", cursor: "pointer", fontSize: "12px" } }, h("input", {
|
|
2098
|
+
type: "checkbox",
|
|
2099
|
+
checked: proxyApplyNpm,
|
|
2100
|
+
onChange: function (e) { setProxyApplyNpm(Boolean(e.target.checked)); },
|
|
2101
|
+
}), "同时应用于 npm 版本探测与一键升级")) : null,
|
|
2102
|
+
// 操作与反馈行:探测/保存按钮 + 结果胶囊
|
|
2103
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" } }, h("button", {
|
|
2104
|
+
className: "dsh-github-btn-sm",
|
|
2105
|
+
style: { padding: "5px 12px", fontSize: "12px" },
|
|
2106
|
+
disabled: proxyBusy || !proxyEnabled,
|
|
2107
|
+
onClick: handleProxyProbe,
|
|
2108
|
+
title: "经当前配置探测 GitHub API 连通性(约 2.5 秒)",
|
|
2109
|
+
}, "🧪 测试连通性"), h("button", {
|
|
2110
|
+
className: "dsh-github-btn-sm",
|
|
2111
|
+
style: {
|
|
2112
|
+
padding: "5px 14px",
|
|
2113
|
+
fontSize: "12px",
|
|
2114
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
2115
|
+
color: "#ffffff",
|
|
2116
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
2117
|
+
fontWeight: 600,
|
|
2118
|
+
},
|
|
2119
|
+
disabled: proxyBusy,
|
|
2120
|
+
onClick: handleProxySave,
|
|
2121
|
+
}, proxyBusy ? "处理中..." : "💾 保存并全局应用"), proxyProbe ? h("span", {
|
|
2122
|
+
style: {
|
|
2123
|
+
fontSize: "12px",
|
|
2124
|
+
fontWeight: 500,
|
|
2125
|
+
color: proxyProbe.ok ? "var(--dsw-alias-state-success, #2ea043)" : "var(--dsw-alias-state-error-primary, #cf222e)",
|
|
2126
|
+
},
|
|
2127
|
+
title: proxyProbe.error || "",
|
|
2128
|
+
}, proxyProbe.ok ? "✅ " + proxyProbe.latencyMs + "ms" : "❌ 探测失败") : null), proxyMsg ? h("div", {
|
|
2129
|
+
style: {
|
|
2130
|
+
marginTop: "8px",
|
|
2131
|
+
fontSize: "12px",
|
|
2132
|
+
lineHeight: "1.5",
|
|
2133
|
+
color: proxyMsg.kind === "ok"
|
|
2134
|
+
? "var(--dsw-alias-state-success, #2ea043)"
|
|
2135
|
+
: proxyMsg.kind === "err"
|
|
2136
|
+
? "var(--dsw-alias-state-error-primary, #cf222e)"
|
|
2137
|
+
: "var(--dsw-alias-label-secondary, #656d76)",
|
|
2138
|
+
},
|
|
2139
|
+
}, proxyMsg.text) : null))),
|
|
2140
|
+
// 底部操作条:sync 分区提供主保存动作;proxy 分区有自己的「保存并全局应用」,
|
|
2141
|
+
// 底部统一为「完成」关闭弹窗,避免两个保存按钮的语义冲突。
|
|
2142
|
+
h("div", { className: "dsh-github-modal-footer" }, activeSection === "sync" ? h("button", {
|
|
2143
|
+
className: "dsh-github-btn-sm",
|
|
2144
|
+
style: {
|
|
2145
|
+
padding: "6px 16px",
|
|
2146
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
2147
|
+
color: "#ffffff",
|
|
2148
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
2149
|
+
fontWeight: 600,
|
|
2150
|
+
},
|
|
2151
|
+
onClick: handleSave,
|
|
2152
|
+
}, "保存同步设置") : null, h("button", {
|
|
2153
|
+
className: "dsh-github-btn-sm",
|
|
2154
|
+
style: { padding: "6px 14px" },
|
|
2155
|
+
onClick: props.onClose,
|
|
2156
|
+
}, activeSection === "sync" ? "取消" : "完成"))));
|
|
2157
|
+
}
|
|
1167
2158
|
function GithubWorkspacePanel() {
|
|
1168
|
-
|
|
1169
|
-
var
|
|
2159
|
+
// 1. 读取本地偏好配置与历史持久化快照 (0ms 纯内存读取)
|
|
2160
|
+
var initialSettings = React.useMemo(() => getLocalSyncSettings(), []);
|
|
2161
|
+
var initialSnapshot = React.useMemo(() => getStoredGlobalOverview(), []);
|
|
2162
|
+
var [syncSettings, setSyncSettings] = React.useState(initialSettings);
|
|
2163
|
+
var [data, setData] = React.useState(initialSnapshot?.data || null);
|
|
2164
|
+
var [loading, setLoading] = React.useState(!initialSnapshot);
|
|
2165
|
+
var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
|
|
2166
|
+
var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
|
|
2167
|
+
var [isSettingsModalOpen, setIsSettingsModalOpen] = React.useState(false);
|
|
1170
2168
|
var [error, setError] = React.useState(null);
|
|
2169
|
+
var [updateInfo, setUpdateInfo] = React.useState(null);
|
|
1171
2170
|
var [activeTab, setActiveTab] = React.useState("workspaces"); // 'workspaces' | 'repos' | 'mywork'
|
|
1172
2171
|
var [searchQuery, setSearchQuery] = React.useState("");
|
|
1173
2172
|
var [issueFilter, setIssueFilter] = React.useState("all"); // 'all' | 'reported' | 'assigned' | 'created'
|
|
2173
|
+
// 代理生效状态(标题栏 🌐 徽章数据源):挂载时读一次 + 弹窗关闭时刷新
|
|
2174
|
+
//(保存发生在弹窗内,关闭时机足以让徽章与最新配置对齐,无需轮询)
|
|
2175
|
+
var [proxyActive, setProxyActive] = React.useState(null);
|
|
2176
|
+
var refreshProxyBadge = React.useCallback(function () {
|
|
2177
|
+
fetch("/api/github/proxy")
|
|
2178
|
+
.then(function (r) { return r.json(); })
|
|
2179
|
+
.then(function (j) {
|
|
2180
|
+
if (j && j.ok && j.state && j.state.config) {
|
|
2181
|
+
setProxyActive({ enabled: Boolean(j.state.config.url), url: j.state.config.url || "" });
|
|
2182
|
+
}
|
|
2183
|
+
else {
|
|
2184
|
+
setProxyActive({ enabled: false, url: "" });
|
|
2185
|
+
}
|
|
2186
|
+
})
|
|
2187
|
+
.catch(function () { });
|
|
2188
|
+
}, []);
|
|
2189
|
+
React.useEffect(function () { refreshProxyBadge(); }, [refreshProxyBadge]);
|
|
2190
|
+
// gh 业务数据就绪后,闲时 1.5 秒异步静默检测升级,绝不影响首屏
|
|
2191
|
+
React.useEffect(() => {
|
|
2192
|
+
if (!data)
|
|
2193
|
+
return;
|
|
2194
|
+
var timer = setTimeout(function () {
|
|
2195
|
+
fetch("/api/github/check-update")
|
|
2196
|
+
.then(function (res) { return res.json(); })
|
|
2197
|
+
.then(function (json) {
|
|
2198
|
+
if (json && json.ok && json.data) {
|
|
2199
|
+
setUpdateInfo(json.data);
|
|
2200
|
+
}
|
|
2201
|
+
})
|
|
2202
|
+
.catch(function () { });
|
|
2203
|
+
}, 1500);
|
|
2204
|
+
return function () { clearTimeout(timer); };
|
|
2205
|
+
}, [data]);
|
|
1174
2206
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
2207
|
+
// 如果是纯手动模式,且非显式强制刷新,且本地已有旧数据,直接跳过后台拉取
|
|
2208
|
+
if (!forceRefresh && syncSettings.strategy === "manual" && data) {
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
1175
2211
|
setLoading(true);
|
|
2212
|
+
setSyncStatus(data ? "syncing" : "loading");
|
|
1176
2213
|
setError(null);
|
|
1177
|
-
// 全局驾驶舱要 spawn 十几次 gh,冷启动可能超过 10 秒。
|
|
1178
|
-
// 不设显式超时的话浏览器会抛一个与真实原因无关的网络错误,
|
|
1179
|
-
// 早先这里会把所有失败都归因成「后端服务未就绪或接口不存在」——归因错误比没有报错更糟。
|
|
1180
2214
|
var controller = new AbortController();
|
|
1181
2215
|
var timedOut = false;
|
|
1182
|
-
var timer = setTimeout(function () { timedOut = true; controller.abort(); },
|
|
2216
|
+
var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
|
|
1183
2217
|
try {
|
|
1184
2218
|
if (forceRefresh)
|
|
1185
2219
|
await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
|
|
1186
2220
|
var res = await fetch("/api/github/global-overview", { signal: controller.signal });
|
|
1187
2221
|
if (!res.ok) {
|
|
1188
2222
|
var errText = await res.text().catch(() => "");
|
|
1189
|
-
|
|
2223
|
+
var failMsg = `获取全局数据失败(HTTP ${res.status})${errText ? ": " + errText : ""}`;
|
|
2224
|
+
if (data) {
|
|
2225
|
+
// 容灾保障:有本地旧数据时绝不红屏崩溃,平滑降级展示离线快照
|
|
2226
|
+
setSyncStatus("failed");
|
|
2227
|
+
}
|
|
2228
|
+
else {
|
|
2229
|
+
setError(failMsg);
|
|
2230
|
+
}
|
|
1190
2231
|
return;
|
|
1191
2232
|
}
|
|
1192
2233
|
var json = await res.json();
|
|
1193
2234
|
if (json.ok && json.data) {
|
|
1194
2235
|
setData(json.data);
|
|
1195
|
-
//
|
|
2236
|
+
saveStoredGlobalOverview(json.data); // 写入持久化快照
|
|
2237
|
+
setLastSyncedAt(Date.now());
|
|
2238
|
+
setSyncStatus("fresh");
|
|
2239
|
+
if (json.data.updateInfo) {
|
|
2240
|
+
setUpdateInfo(json.data.updateInfo);
|
|
2241
|
+
}
|
|
1196
2242
|
if (json.data.workspaceError) {
|
|
1197
2243
|
setError("工作区列表读取失败:" + json.data.workspaceError);
|
|
1198
2244
|
}
|
|
1199
2245
|
}
|
|
1200
2246
|
else {
|
|
1201
|
-
|
|
2247
|
+
if (data) {
|
|
2248
|
+
setSyncStatus("failed");
|
|
2249
|
+
}
|
|
2250
|
+
else {
|
|
2251
|
+
setError(json.error || "获取全局数据失败");
|
|
2252
|
+
}
|
|
1202
2253
|
}
|
|
1203
2254
|
}
|
|
1204
2255
|
catch (err) {
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
2256
|
+
if (data) {
|
|
2257
|
+
// 后台网络故障时,旧数据依然完整可用
|
|
2258
|
+
setSyncStatus("failed");
|
|
1207
2259
|
}
|
|
1208
2260
|
else {
|
|
1209
|
-
|
|
2261
|
+
if (timedOut || err.name === "AbortError") {
|
|
2262
|
+
setError("获取超时(30 秒):本机 gh 命令响应过慢,通常是网络或工作区数量较多所致。可稍后重试,或减少同时打开的工作区。");
|
|
2263
|
+
}
|
|
2264
|
+
else {
|
|
2265
|
+
setError("无法连接后端服务:" + (err.message || err));
|
|
2266
|
+
}
|
|
1210
2267
|
}
|
|
1211
2268
|
}
|
|
1212
2269
|
finally {
|
|
1213
2270
|
clearTimeout(timer);
|
|
1214
2271
|
setLoading(false);
|
|
1215
2272
|
}
|
|
1216
|
-
}, []);
|
|
1217
|
-
|
|
1218
|
-
//
|
|
2273
|
+
}, [data, syncSettings]);
|
|
2274
|
+
// 挂载加载调度:SWR 模式下初次延迟 300ms 后台平滑发起静默同步。
|
|
2275
|
+
// 无论本地是否有旧快照都拉:昨天的持久化快照在挂载后 300ms 内自愈,
|
|
2276
|
+
// 「打开驾驶舱即新鲜」由 SWR 定义本身保证,而非依赖用户停留或手动刷新。
|
|
2277
|
+
React.useEffect(() => {
|
|
2278
|
+
if (syncSettings.strategy === "swr") {
|
|
2279
|
+
var timer = setTimeout(function () { loadData(false); }, 300);
|
|
2280
|
+
return function () { clearTimeout(timer); };
|
|
2281
|
+
}
|
|
2282
|
+
else if (!data) {
|
|
2283
|
+
loadData(false);
|
|
2284
|
+
}
|
|
2285
|
+
}, [syncSettings.strategy]);
|
|
2286
|
+
// (原定时轮询调度已随 'interval' 策略一并移除)
|
|
2287
|
+
// 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
|
|
1219
2288
|
React.useEffect(() => {
|
|
1220
2289
|
var onFocus = () => {
|
|
1221
2290
|
if (!data?.auth?.loggedIn) {
|
|
1222
|
-
|
|
2291
|
+
fetch("/api/github/auth?force=true")
|
|
2292
|
+
.then(function (res) { return res.json(); })
|
|
2293
|
+
.then(function (json) {
|
|
2294
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
2295
|
+
clearStoredGlobalOverview();
|
|
2296
|
+
loadData(true);
|
|
2297
|
+
}
|
|
2298
|
+
})
|
|
2299
|
+
.catch(function () { });
|
|
1223
2300
|
}
|
|
1224
2301
|
};
|
|
1225
2302
|
window.addEventListener("focus", onFocus);
|
|
1226
2303
|
return () => window.removeEventListener("focus", onFocus);
|
|
1227
2304
|
}, [data, loadData]);
|
|
1228
2305
|
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 全景驾驶舱")
|
|
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 }),
|
|
2307
|
+
// 代理生效徽章:启用时可见,hover 展示代理地址,让用户随时确认「当前确实在走代理」
|
|
2308
|
+
proxyActive && proxyActive.enabled
|
|
2309
|
+
? h("span", {
|
|
2310
|
+
style: {
|
|
2311
|
+
display: "inline-flex",
|
|
2312
|
+
alignItems: "center",
|
|
2313
|
+
gap: "4px",
|
|
2314
|
+
fontSize: "11px",
|
|
2315
|
+
fontWeight: 500,
|
|
2316
|
+
padding: "2px 8px",
|
|
2317
|
+
borderRadius: "999px",
|
|
2318
|
+
background: "rgba(9, 105, 218, 0.1)",
|
|
2319
|
+
border: "1px solid rgba(9, 105, 218, 0.35)",
|
|
2320
|
+
color: "var(--dsw-alias-link, #0969da)",
|
|
2321
|
+
cursor: "default",
|
|
2322
|
+
},
|
|
2323
|
+
title: "插件网络代理已启用: " + proxyActive.url + "(GitHub 数据链路与工具调用经此代理;点击 ⚙️ 可修改)",
|
|
2324
|
+
}, "🌐 代理")
|
|
2325
|
+
: null), h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, h("button", {
|
|
1230
2326
|
className: "dsh-github-refresh-btn",
|
|
1231
2327
|
onClick: () => loadData(true),
|
|
1232
2328
|
disabled: loading,
|
|
1233
|
-
}, loading ? "
|
|
2329
|
+
}, loading ? "同步中..." : "🔄 刷新全局数据"), h("button", {
|
|
2330
|
+
className: "dsh-github-btn-sm",
|
|
2331
|
+
onClick: () => setIsSettingsModalOpen(true),
|
|
2332
|
+
title: "配置数据同步节奏、本地缓存与网络代理",
|
|
2333
|
+
style: { padding: "6px 12px" },
|
|
2334
|
+
}, "⚙️ 设置")));
|
|
1234
2335
|
if (loading && !data) {
|
|
1235
2336
|
return h("div", { className: "dsh-github-panel" }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
|
|
1236
2337
|
}
|
|
2338
|
+
var effectiveUpdateInfo = updateInfo || data?.updateInfo;
|
|
2339
|
+
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
2340
|
+
? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar: false })
|
|
2341
|
+
: null;
|
|
2342
|
+
var settingsModalEl = h(GlobalSyncSettingsModal, {
|
|
2343
|
+
isOpen: isSettingsModalOpen,
|
|
2344
|
+
onClose: () => {
|
|
2345
|
+
setIsSettingsModalOpen(false);
|
|
2346
|
+
// 弹窗内可能刚保存过代理配置,关闭时同步刷新标题栏徽章
|
|
2347
|
+
refreshProxyBadge();
|
|
2348
|
+
},
|
|
2349
|
+
currentSettings: syncSettings,
|
|
2350
|
+
onSave: (newSettings) => {
|
|
2351
|
+
setSyncSettings(newSettings);
|
|
2352
|
+
},
|
|
2353
|
+
onClearCache: () => {
|
|
2354
|
+
setSyncStatus("loading");
|
|
2355
|
+
setData(null);
|
|
2356
|
+
setLastSyncedAt(0);
|
|
2357
|
+
},
|
|
2358
|
+
});
|
|
2359
|
+
// 错误拦截阻断:若无本地数据且请求失败,直接呈现错误卡片,杜绝把接口异常伪装成「未安装 CLI」
|
|
2360
|
+
if (!data && error) {
|
|
2361
|
+
return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, h("div", {
|
|
2362
|
+
style: {
|
|
2363
|
+
padding: "24px",
|
|
2364
|
+
borderRadius: "8px",
|
|
2365
|
+
background: "rgba(248, 81, 73, 0.08)",
|
|
2366
|
+
border: "1px solid rgba(248, 81, 73, 0.3)",
|
|
2367
|
+
margin: "16px 0",
|
|
2368
|
+
textAlign: "center",
|
|
2369
|
+
},
|
|
2370
|
+
}, 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", {
|
|
2371
|
+
className: "dsh-github-btn-sm",
|
|
2372
|
+
onClick: () => loadData(true),
|
|
2373
|
+
style: { padding: "6px 18px", background: "var(--dsw-alias-link, #0969da)", color: "#ffffff", borderColor: "var(--dsw-alias-link, #0969da)" },
|
|
2374
|
+
}, "🔄 重新请求")), settingsModalEl);
|
|
2375
|
+
}
|
|
1237
2376
|
var auth = data ? data.auth : null;
|
|
1238
2377
|
var userRepos = (data && data.userRepos) || [];
|
|
1239
2378
|
var workspaceMatrix = (data && data.workspaceMatrix) || [];
|
|
@@ -1329,7 +2468,7 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1329
2468
|
})));
|
|
1330
2469
|
}
|
|
1331
2470
|
}
|
|
1332
|
-
return h("div", { className: "dsh-github-panel" }, headerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl);
|
|
2471
|
+
return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
|
|
1333
2472
|
}
|
|
1334
2473
|
var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme"];
|
|
1335
2474
|
function apply(ctx) {
|