@nimoxie/dsh-ui-tweaks 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/DEVELOPMENT.md +147 -116
- package/LICENSE +21 -21
- package/README.md +157 -137
- package/cordis.patch.yml +7 -7
- package/lib/client.js +448 -30
- package/lib/window-helper.cs +195 -195
- package/lib/window-helper.ps1 +276 -276
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -14,7 +14,7 @@ window.__ModuleLoader__.load({
|
|
|
14
14
|
const module = { exports: {} };
|
|
15
15
|
const exports = module.exports;
|
|
16
16
|
|
|
17
|
-
const BUILD = "0.
|
|
17
|
+
const BUILD = "0.2.0";
|
|
18
18
|
|
|
19
19
|
/* 每个模块的注册各套一层 guard:失败只记 warn(座位/模块失败模式是「静默消失」,
|
|
20
20
|
必须主动吭声;但不能让一个模块的异常打断同文件里其它模块的 apply)。 */
|
|
@@ -390,6 +390,19 @@ window.__ModuleLoader__.load({
|
|
|
390
390
|
var BTN_ID = "dshuiw-updates-btn-host";
|
|
391
391
|
var RBTN_ID = "dshuiw-restart-btn-host";
|
|
392
392
|
var PANEL_ID = "dshuiw-updates-panel-host";
|
|
393
|
+
/* 用户是否主动收起了面板。paint() 只负责重画,**不负责把面板弹出来** ——
|
|
394
|
+
否则后台一有动静(装完一个包、冷静期到点自动重查、超时兜底)就会把用户
|
|
395
|
+
刚收起来的面板又弹回来。显示只由「打开」路径决定,隐藏只由「收起」路径决定。 */
|
|
396
|
+
var panelClosed = false;
|
|
397
|
+
/* 弹窗里的控件都是自绘 span(渲染结构与 CSS 都不动),键盘得自己接:
|
|
398
|
+
可点的补 role=button/tabindex=0(KBD),Enter/Space 派发一次 click,
|
|
399
|
+
走同一套点击委托,行为与鼠标完全一致。 */
|
|
400
|
+
var KBD = ' role="button" tabindex="0"';
|
|
401
|
+
/* 焦点环用官方自己的令牌(app 里有 237 条 :focus-visible,主题变量就是
|
|
402
|
+
--dsw-focus-ring-*),与原生控件的焦点样式同色同宽,不自造灰色。
|
|
403
|
+
三处使用点:两个 shadow 样式 + 页面级那两枚按钮。 */
|
|
404
|
+
var FOCUS_RING = "outline:var(--dsw-focus-ring-width,2px) solid " +
|
|
405
|
+
"var(--dsw-focus-ring-color,var(--dsw-alias-state-business-primary,rgba(120,170,255,.9)));outline-offset:2px";
|
|
393
406
|
|
|
394
407
|
/* ---- semver 比较(rc 比数字/字母、正式版 > 预发布版;字符串比会把
|
|
395
408
|
rc.9 判成大于 rc.10,必须自己实现)---- */
|
|
@@ -738,6 +751,7 @@ window.__ModuleLoader__.load({
|
|
|
738
751
|
**先问会话有没有在跑** —— 没有在跑的会话就直接执行,不打扰人;有才弹中断提示。
|
|
739
752
|
宿主形态决定重启怎么做(shell / relaunch / manual),文案按形态给。 */
|
|
740
753
|
var RST_PANEL_ID = 'dshuiw-restart-panel-host';
|
|
754
|
+
var scrimShown = false; // 蒙皮是否正盖在页面上(键盘滚动锁的开关)
|
|
741
755
|
var rstStage = ''; // '' | checking | auto | confirm | busy | done | error
|
|
742
756
|
var rstMode = ''; // relaunch | shell | manual
|
|
743
757
|
var rstNote = '';
|
|
@@ -826,6 +840,7 @@ window.__ModuleLoader__.load({
|
|
|
826
840
|
'background:var(--dsw-alias-bg-base,#1b1d23);color:var(--dsw-alias-label-primary,#e8e9ec);' +
|
|
827
841
|
'box-shadow:0 10px 30px rgba(0,0,0,.34);font:13px/1.55 system-ui,-apple-system,"Microsoft YaHei UI",sans-serif}' +
|
|
828
842
|
'.rwrap[hidden]{display:none}' +
|
|
843
|
+
'[role="button"]:focus-visible{' + FOCUS_RING + '}' +
|
|
829
844
|
'.rt{font-weight:600;margin-bottom:6px}' +
|
|
830
845
|
'.rb{font-size:12px;line-height:1.5;opacity:.85;white-space:pre-wrap;word-break:break-word}' +
|
|
831
846
|
'.rl{margin:6px 0 0 10px;font-size:12px;opacity:.9}' +
|
|
@@ -840,7 +855,7 @@ window.__ModuleLoader__.load({
|
|
|
840
855
|
'.rscrim{position:fixed;top:0;left:0;right:0;bottom:0;z-index:2147483000;cursor:progress;' +
|
|
841
856
|
'background:rgba(8,10,14,.5);backdrop-filter:blur(1.5px);-webkit-backdrop-filter:blur(1.5px)}' +
|
|
842
857
|
'.rscrim[hidden]{display:none}' +
|
|
843
|
-
'</style><div class="rscrim" hidden></div><div class="rwrap" hidden></div>';
|
|
858
|
+
'</style><div class="rscrim" hidden></div><div class="rwrap" role="dialog" aria-modal="true" aria-label="重启 dsh" hidden></div>';
|
|
844
859
|
var scrim = sr.querySelector('.rscrim');
|
|
845
860
|
try {
|
|
846
861
|
scrim.addEventListener('wheel', function (e) { e.preventDefault(); }, { passive: false });
|
|
@@ -852,7 +867,7 @@ window.__ModuleLoader__.load({
|
|
|
852
867
|
if (!t || !t.classList) { return; }
|
|
853
868
|
if (t.classList.contains('rgo')) { restartGo(); return; }
|
|
854
869
|
if (t.classList.contains('rretry')) { restartOpen(); return; }
|
|
855
|
-
if (t.classList.contains('rcancel') || t.classList.contains('rdismiss')) { restartClose(); return; }
|
|
870
|
+
if (t.classList.contains('rcancel') || t.classList.contains('rdismiss')) { restartClose(true); return; }
|
|
856
871
|
});
|
|
857
872
|
/* 点外部/Esc 收起;点自己那枚按钮不算外部(交给按钮做开关) */
|
|
858
873
|
document.addEventListener('pointerdown', function (e) {
|
|
@@ -865,18 +880,46 @@ window.__ModuleLoader__.load({
|
|
|
865
880
|
document.addEventListener('keydown', function (e) {
|
|
866
881
|
if (e.key !== 'Escape' || rstStage === 'busy') { return; }
|
|
867
882
|
var cur = host.shadowRoot.querySelector('.rwrap');
|
|
868
|
-
if (cur && !cur.hidden) { restartClose(); }
|
|
883
|
+
if (cur && !cur.hidden) { restartClose(true); }
|
|
869
884
|
});
|
|
885
|
+
/* 蒙皮期间页面已 inert,但键盘仍可能滚动后面的文档(PageDown/空格/方向键):
|
|
886
|
+
在捕获段拦掉纯滚动键。焦点在弹窗里时 e.target 会被重定向成宿主,那时不拦 ——
|
|
887
|
+
弹窗该能自己滚,按钮也该能用空格触发。 */
|
|
888
|
+
document.addEventListener('keydown', function (e) {
|
|
889
|
+
if (!scrimShown) { return; }
|
|
890
|
+
var hostEl = document.getElementById(RST_PANEL_ID);
|
|
891
|
+
if (hostEl && (e.target === hostEl || hostEl.contains(e.target))) { return; }
|
|
892
|
+
var k = e.key;
|
|
893
|
+
if (k === 'PageDown' || k === 'PageUp' || k === 'ArrowDown' || k === 'ArrowUp' ||
|
|
894
|
+
k === 'Home' || k === 'End' || k === ' ' || k === 'Spacebar') {
|
|
895
|
+
e.preventDefault();
|
|
896
|
+
}
|
|
897
|
+
}, true);
|
|
898
|
+
wireKeyActivation(wrap);
|
|
870
899
|
return wrap;
|
|
871
900
|
}
|
|
872
901
|
|
|
873
902
|
/* ---- 蒙皮:确认重启后把整页蒙住(客户端此时已不可用,界面元素不该还能点)---- */
|
|
903
|
+
/* 蒙皮只挡指针。键盘仍能 Tab 到后面的元素并回车激活,所以同时把 body 的其它
|
|
904
|
+
子树整块 inert 掉(Chromium 102+ 支持;弹窗自己的宿主除外)。关掉时如实还原。 */
|
|
905
|
+
function setPageInert(on) {
|
|
906
|
+
try {
|
|
907
|
+
var kids = document.body.children;
|
|
908
|
+
for (var i = 0; i < kids.length; i++) {
|
|
909
|
+
var el = kids[i];
|
|
910
|
+
if (!el || el.id === RST_PANEL_ID) { continue; }
|
|
911
|
+
if (on) { el.setAttribute("inert", ""); } else { el.removeAttribute("inert"); }
|
|
912
|
+
}
|
|
913
|
+
} catch (e) {}
|
|
914
|
+
}
|
|
874
915
|
function showScrim(on) {
|
|
875
916
|
try {
|
|
876
917
|
rstWrap(); // 确保阴影根在(隐藏的弹窗也没关系)
|
|
877
918
|
var host = document.getElementById(RST_PANEL_ID);
|
|
878
919
|
var s = host && host.shadowRoot ? host.shadowRoot.querySelector('.rscrim') : null;
|
|
879
920
|
if (s) { s.hidden = !on; }
|
|
921
|
+
scrimShown = !!on;
|
|
922
|
+
setPageInert(on);
|
|
880
923
|
} catch (e) {}
|
|
881
924
|
}
|
|
882
925
|
var rstKillTimer = 0;
|
|
@@ -899,7 +942,7 @@ window.__ModuleLoader__.load({
|
|
|
899
942
|
|
|
900
943
|
function rstRender() {
|
|
901
944
|
var wrap = rstWrap();
|
|
902
|
-
function btn(cls, label) { return '<span class="rbtn ' + cls + '">' + label + '</span>'; }
|
|
945
|
+
function btn(cls, label) { return '<span class="rbtn ' + cls + '"' + KBD + '>' + label + '</span>'; }
|
|
903
946
|
var title = '重启 dsh';
|
|
904
947
|
var body = '';
|
|
905
948
|
var actions = '';
|
|
@@ -939,14 +982,25 @@ window.__ModuleLoader__.load({
|
|
|
939
982
|
if (wrap.innerHTML !== html) { wrap.innerHTML = html; }
|
|
940
983
|
wrap.hidden = false;
|
|
941
984
|
placeNear(rstBtn, wrap, RST_WIDTH, 180);
|
|
985
|
+
/* 键盘:确认/失败/收尾态把焦点交给主按钮;弹窗里已经有焦点就不抢 */
|
|
986
|
+
if (rstStage === 'confirm' || rstStage === 'error' || rstStage === 'done') {
|
|
987
|
+
try {
|
|
988
|
+
var hostEl = document.getElementById(RST_PANEL_ID);
|
|
989
|
+
var inside = hostEl && hostEl.shadowRoot ? hostEl.shadowRoot.activeElement : null;
|
|
990
|
+
var primary = wrap.querySelector('.rgo, .rretry, .rdismiss, .rcancel');
|
|
991
|
+
if (primary && !inside) { primary.focus(); }
|
|
992
|
+
} catch (e) {}
|
|
993
|
+
}
|
|
942
994
|
}
|
|
943
995
|
|
|
944
|
-
function restartClose() {
|
|
996
|
+
function restartClose(refocus) {
|
|
945
997
|
showScrim(false);
|
|
946
998
|
clearKillTimer();
|
|
947
999
|
var wrap = rstWrap();
|
|
948
1000
|
wrap.hidden = true;
|
|
949
1001
|
rstStage = ''; rstNote = ''; rstActive = null; rstNames = [];
|
|
1002
|
+
/* 键盘:收起后把焦点还给「立即重启」。点外部收起的传 false */
|
|
1003
|
+
if (refocus) { try { if (rstBtn && document.contains(rstBtn)) { rstBtn.focus(); } } catch (e) {} }
|
|
950
1004
|
}
|
|
951
1005
|
|
|
952
1006
|
/* 打开:并行读「重启方案」与「在跑的会话」,两者都回来再决定 */
|
|
@@ -1049,7 +1103,7 @@ window.__ModuleLoader__.load({
|
|
|
1049
1103
|
var stage = forceStage[name] || "";
|
|
1050
1104
|
var ver = r.latest || r.version;
|
|
1051
1105
|
function btn(cls, label) {
|
|
1052
|
-
return "<span class=\"fbtn " + cls + "\" data-n=\"" + esc(name) + "\">" + label + "</span>";
|
|
1106
|
+
return "<span class=\"fbtn " + cls + "\" data-n=\"" + esc(name) + "\"" + KBD + ">" + label + "</span>";
|
|
1053
1107
|
}
|
|
1054
1108
|
if (stage === "checking") { return "<div class=\"force\"><div class=\"fn\">正在预检…</div></div>"; }
|
|
1055
1109
|
if (stage === "writing") { return "<div class=\"force\"><div class=\"fn\">正在写入豁免表…</div></div>"; }
|
|
@@ -1589,7 +1643,26 @@ window.__ModuleLoader__.load({
|
|
|
1589
1643
|
来源:触发它的那枚按钮。按钮被页面布局推走(滚动、窗口尺寸变化、插件页重排)
|
|
1590
1644
|
时弹窗跟着走,不会有"拖到哪就钉在哪、之后再也不跟"的状态。 */
|
|
1591
1645
|
var PANEL_GAP = 6, PANEL_MARGIN = 8;
|
|
1646
|
+
/* 兜底宽度:只在弹窗还没渲染、量不到布局时用 */
|
|
1592
1647
|
var PANEL_WIDTH = 344, RST_WIDTH = 330;
|
|
1648
|
+
/* 量真实外框。调用方传的 width 只是 CSS 里的**内容宽**,而两个弹窗的盒模型并不一样:
|
|
1649
|
+
Shadow DOM 里拿不到官方全局的 box-sizing 复位,.rwrap 是 content-box 且带 12px
|
|
1650
|
+
padding → 实宽比常量多 26px,右缘会比按钮多探出 26px(.wrap 没有 padding,
|
|
1651
|
+
只多 2px 边框,所以看着是对齐的)。竖直方向同理:maxHeight 约束的是内容盒,
|
|
1652
|
+
不扣掉 padding+border,弹窗就会比算出来的空间高出 26px。两边都用实测值。 */
|
|
1653
|
+
function measuredBox(wrap, fallbackWidth) {
|
|
1654
|
+
/* 宽度用 getBoundingClientRect:和锚点矩形同一个坐标系,页面若带缩放也不会串 */
|
|
1655
|
+
var width = 0;
|
|
1656
|
+
try { width = wrap.getBoundingClientRect().width; } catch (e) {}
|
|
1657
|
+
if (!width) { width = wrap.offsetWidth || fallbackWidth; }
|
|
1658
|
+
var extraY = 0;
|
|
1659
|
+
try {
|
|
1660
|
+
var cs = window.getComputedStyle(wrap);
|
|
1661
|
+
var n = function (v) { var f = parseFloat(v); return isFinite(f) ? f : 0; };
|
|
1662
|
+
extraY = n(cs.paddingTop) + n(cs.paddingBottom) + n(cs.borderTopWidth) + n(cs.borderBottomWidth);
|
|
1663
|
+
} catch (e) {}
|
|
1664
|
+
return { width: width, extraY: extraY };
|
|
1665
|
+
}
|
|
1593
1666
|
function placeNear(anchor, wrap, width, minHeight) {
|
|
1594
1667
|
try {
|
|
1595
1668
|
if (!wrap || wrap.hidden) { return; }
|
|
@@ -1597,14 +1670,20 @@ window.__ModuleLoader__.load({
|
|
|
1597
1670
|
var r = null;
|
|
1598
1671
|
if (anchor && anchor.getBoundingClientRect) { r = anchor.getBoundingClientRect(); }
|
|
1599
1672
|
if (!r || (!r.width && !r.height)) { r = { right: vw - 16, top: 56, bottom: 56 }; } // 按钮还没挂上:退回右上角
|
|
1600
|
-
var
|
|
1601
|
-
var
|
|
1673
|
+
var box = measuredBox(wrap, width);
|
|
1674
|
+
var outer = box.width;
|
|
1675
|
+
var left = Math.max(PANEL_MARGIN, Math.min(vw - outer - PANEL_MARGIN, r.right - outer));
|
|
1676
|
+
/* scrollHeight 是未被 maxHeight 夹过的自然高度:反复重排时判据不会漂 */
|
|
1677
|
+
var height = wrap.scrollHeight || wrap.offsetHeight || minHeight || 200;
|
|
1602
1678
|
var below = vh - r.bottom - PANEL_GAP - PANEL_MARGIN;
|
|
1603
1679
|
var above = r.top - PANEL_GAP - PANEL_MARGIN;
|
|
1604
1680
|
/* 优先放下方;下方放不下(或不如上方宽裕)就翻到上方 */
|
|
1605
1681
|
var useBelow = below >= Math.min(height, minHeight || 200) || below >= above;
|
|
1606
|
-
|
|
1607
|
-
|
|
1682
|
+
/* 不再把 room 强行抬到 120:按钮贴着视口上下边时那会突破边距,
|
|
1683
|
+
让弹窗(连同底部的「重启 dsh / 取消」)跑到视口外面去。
|
|
1684
|
+
真正的下限交给下面 maxHeight 的 60。 */
|
|
1685
|
+
var room = useBelow ? below : above;
|
|
1686
|
+
wrap.style.maxHeight = Math.max(60, room - box.extraY) + 'px';
|
|
1608
1687
|
wrap.style.left = left + 'px';
|
|
1609
1688
|
wrap.style.right = 'auto';
|
|
1610
1689
|
wrap.style.top = (useBelow
|
|
@@ -1612,6 +1691,24 @@ window.__ModuleLoader__.load({
|
|
|
1612
1691
|
: Math.max(PANEL_MARGIN, r.top - PANEL_GAP - Math.min(height, room))) + 'px';
|
|
1613
1692
|
} catch (e) {}
|
|
1614
1693
|
}
|
|
1694
|
+
function wireKeyActivation(wrap) {
|
|
1695
|
+
wrap.addEventListener("keydown", function (e) {
|
|
1696
|
+
if (e.key !== "Enter" && e.key !== " " && e.key !== "Spacebar") { return; }
|
|
1697
|
+
var t = e.target;
|
|
1698
|
+
if (!t || !t.getAttribute || t.getAttribute("role") !== "button") { return; }
|
|
1699
|
+
e.preventDefault(); // Space 默认会滚动页面
|
|
1700
|
+
try { t.click(); } catch (e2) {}
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
/* 收起更新面板:置位 + 隐藏,状态不动(后台的安装/检查继续跑) */
|
|
1704
|
+
function closePanel(refocus) {
|
|
1705
|
+
panelClosed = true;
|
|
1706
|
+
var w = liveWrap(PANEL_ID, ".wrap");
|
|
1707
|
+
if (w) { w.hidden = true; }
|
|
1708
|
+
/* 键盘:收起后把焦点还给触发它的那枚按钮。点外部收起的传 false ——
|
|
1709
|
+
焦点已经跟着用户点的地方去了,不该抢回来。 */
|
|
1710
|
+
if (refocus) { try { var b = document.getElementById(BTN_ID); if (b) { b.focus(); } } catch (e) {} }
|
|
1711
|
+
}
|
|
1615
1712
|
/* 已经建出来且开着的弹窗;重排时不要顺手把它们创建出来 */
|
|
1616
1713
|
function liveWrap(hostId, sel) {
|
|
1617
1714
|
var host = document.getElementById(hostId);
|
|
@@ -1635,7 +1732,17 @@ window.__ModuleLoader__.load({
|
|
|
1635
1732
|
}
|
|
1636
1733
|
try {
|
|
1637
1734
|
window.addEventListener('resize', relayoutPanels);
|
|
1638
|
-
|
|
1735
|
+
/* 面板自己内部滚动(.rows)不影响按钮位置,不必重排;用 composedPath 认出来 */
|
|
1736
|
+
window.addEventListener('scroll', function (e) {
|
|
1737
|
+
if (e && typeof e.composedPath === 'function') {
|
|
1738
|
+
var path = e.composedPath();
|
|
1739
|
+
for (var i = 0; i < path.length; i++) {
|
|
1740
|
+
var n = path[i];
|
|
1741
|
+
if (n && n.id && (n.id === PANEL_ID || n.id === RST_PANEL_ID)) { return; }
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
relayoutPanels();
|
|
1745
|
+
}, true);
|
|
1639
1746
|
} catch (e) {}
|
|
1640
1747
|
|
|
1641
1748
|
/* ---- 面板 UI:挂在 body 的 Shadow DOM(fixed 浮层不能放进 flex 工具栏)---- */
|
|
@@ -1655,6 +1762,7 @@ window.__ModuleLoader__.load({
|
|
|
1655
1762
|
"box-shadow:0 10px 30px rgba(0,0,0,.34);" +
|
|
1656
1763
|
"font:13px/1.55 system-ui,-apple-system,\"Microsoft YaHei UI\",sans-serif}" +
|
|
1657
1764
|
".wrap[hidden]{display:none}" +
|
|
1765
|
+
"[role=\"button\"]:focus-visible{" + FOCUS_RING + "}" +
|
|
1658
1766
|
".hd{display:flex;align-items:center;gap:8px;padding:10px 12px;flex:0 0 auto;" +
|
|
1659
1767
|
"border-bottom:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.22));" +
|
|
1660
1768
|
"user-select:none;-webkit-user-select:none}" +
|
|
@@ -1712,7 +1820,7 @@ window.__ModuleLoader__.load({
|
|
|
1712
1820
|
"border-bottom:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.18))}" +
|
|
1713
1821
|
".foot{padding:8px 12px;font-size:11px;line-height:1.5;opacity:.6;" +
|
|
1714
1822
|
"border-top:1px solid var(--dsw-alias-border-l2,rgba(128,128,128,.12))}" +
|
|
1715
|
-
"</style><div class=\"wrap\" hidden></div>";
|
|
1823
|
+
"</style><div class=\"wrap\" role=\"dialog\" aria-label=\"插件更新\" tabindex=\"-1\" hidden></div>";
|
|
1716
1824
|
var wrap = sr.querySelector(".wrap");
|
|
1717
1825
|
|
|
1718
1826
|
/* 不可拖动:位置完全由 placeNear() 决定(见文件上方的「弹窗定位」)。
|
|
@@ -1726,19 +1834,21 @@ window.__ModuleLoader__.load({
|
|
|
1726
1834
|
var cur = host.shadowRoot.querySelector(".wrap");
|
|
1727
1835
|
if (!cur || cur.hidden) { return; }
|
|
1728
1836
|
if (host.contains(e.target)) { return; }
|
|
1729
|
-
|
|
1837
|
+
var chkBtn = document.getElementById(BTN_ID);
|
|
1838
|
+
if (chkBtn && (chkBtn === e.target || chkBtn.contains(e.target))) { return; }
|
|
1839
|
+
closePanel();
|
|
1730
1840
|
}, true);
|
|
1731
1841
|
document.addEventListener("keydown", function (e) {
|
|
1732
1842
|
if (e.key !== "Escape") { return; }
|
|
1733
1843
|
var cur = host.shadowRoot.querySelector(".wrap");
|
|
1734
|
-
if (cur
|
|
1844
|
+
if (cur && !cur.hidden) { closePanel(true); }
|
|
1735
1845
|
});
|
|
1736
1846
|
|
|
1737
1847
|
/* 行内点击委托:有新版本 → 入队;更新中/等待 → 取消;失败 → 重试 */
|
|
1738
1848
|
wrap.addEventListener("click", function (e) {
|
|
1739
1849
|
var t = e.target;
|
|
1740
1850
|
if (!t || !t.classList) { return; }
|
|
1741
|
-
if (t.classList.contains("x")) {
|
|
1851
|
+
if (t.classList.contains("x")) { closePanel(true); return; }
|
|
1742
1852
|
if (t.classList.contains("recheck")) { if (!checking) { checkNow(); } return; }
|
|
1743
1853
|
if (t.classList.contains("reverify")) { if (!checking) { checkNow(); } return; }
|
|
1744
1854
|
if (t.classList.contains("allbtn")) {
|
|
@@ -1783,6 +1893,8 @@ window.__ModuleLoader__.load({
|
|
|
1783
1893
|
}
|
|
1784
1894
|
});
|
|
1785
1895
|
|
|
1896
|
+
wireKeyActivation(wrap);
|
|
1897
|
+
|
|
1786
1898
|
/* 状态 → 行渲染订阅 */
|
|
1787
1899
|
listeners.push(function () { paint(); });
|
|
1788
1900
|
return wrap;
|
|
@@ -1835,12 +1947,19 @@ window.__ModuleLoader__.load({
|
|
|
1835
1947
|
else if (r.state === "fail") { cls = "tag error act clickable"; act = " data-n=\"" + esc(r.name) + "\""; title = " title=\"" + esc(r.why || "更新失败") + "(点击重试)\""; }
|
|
1836
1948
|
else if (r.state === "unchanged") { cls = "tag update act clickable"; act = " data-n=\"" + esc(r.name) + "\""; title = " title=\"" + esc(r.why || "") + "(点击重试)\""; }
|
|
1837
1949
|
else if (r.state === "unconfirmed") { cls = "tag other reverify clickable"; title = " title=\"" + esc(r.why || "") + "(点击重查)\""; }
|
|
1838
|
-
return "<span class=\"" + cls + "\"" + act + title + ">" + esc(TAG_TEXT[r.state] || "未比对") + "</span>";
|
|
1839
|
-
}
|
|
1840
|
-
/* ----
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1950
|
+
return "<span class=\"" + cls + "\"" + act + title + (cls.indexOf("clickable") >= 0 ? KBD : "") + ">" + esc(TAG_TEXT[r.state] || "未比对") + "</span>";
|
|
1951
|
+
}
|
|
1952
|
+
/* ---- 「自动审查」访问模式提示(只提示,不改任何设置)--------------------
|
|
1953
|
+
两件事都跟这个模式有关,所以合成一条提醒:
|
|
1954
|
+
① DSH 0.2.0-rc.2 的已知缺陷:访问模式为 auto 的会话会让 permissionPresets 服务在
|
|
1955
|
+
热重载(装/更新插件就会触发)时构造失败 —— 表现是装插件报一长串错、助手侧也随之
|
|
1956
|
+
一连串报错,且要重启客户端才恢复。
|
|
1957
|
+
② 该模式对每次工具调用还有一道「调用前审查」,审查服务不可用或限流时这次调用会被
|
|
1958
|
+
直接拒绝、工具体不执行(文案形如 Auto review of tool "…" failed)。
|
|
1959
|
+
0.2.1-rc.2 复核(2026-10-18):预设标识仍是 AUTO_PRESET = "auto"(界面改叫
|
|
1960
|
+
「自动审查」,判定串 currentValue === "auto" 不受影响);旧缺陷的 throw 仍在,
|
|
1961
|
+
但 auto-review 集成新增了卸载时的会话迁移(先把 auto 会话改成 danger-full-access
|
|
1962
|
+
再退出),触发窗口已收窄。这里只读当前是否有这种会话,用来提醒用户先切走。
|
|
1844
1963
|
读法同官方权限面板:sessions 列表 → binding(id).session → permissions 投影。
|
|
1845
1964
|
拿不到服务/投影(老版本 dsh、会话未驻留)就返回 false,宁可不提示也不误报。 */
|
|
1846
1965
|
function hasAutoReviewSession() {
|
|
@@ -1865,7 +1984,7 @@ window.__ModuleLoader__.load({
|
|
|
1865
1984
|
}
|
|
1866
1985
|
function accessModeBar() {
|
|
1867
1986
|
if (!hasAutoReviewSession()) { return ""; }
|
|
1868
|
-
return "<div class=\"warn\">⚠
|
|
1987
|
+
return "<div class=\"warn\">⚠ 有会话正在用「自动审查」(Auto review)访问模式:这个模式下装/更新插件有时会失败,助手这边也会跟着一连串报错,要重启客户端才能恢复;该模式下每次工具调用还会先过一遍审查,审查服务不可用或限流时,这次调用会被直接拒绝、不执行。建议先把该会话切换成「工作区内修改」,装完再切回「自动审查」。</div>";
|
|
1869
1988
|
}
|
|
1870
1989
|
|
|
1871
1990
|
function paint() {
|
|
@@ -1879,7 +1998,7 @@ window.__ModuleLoader__.load({
|
|
|
1879
1998
|
(isDesktop ? "重启客户端后生效" : "重启 dsh 服务并刷新页面后生效") + "</div>";
|
|
1880
1999
|
}
|
|
1881
2000
|
rows.forEach(function (r) { if (r.state === "update") { upd++; } });
|
|
1882
|
-
var allBtn = upd ? "<span class=\"allbtn\">全部更新(" + upd + ")</span>" : "<span class=\"allbtn\" hidden></span>";
|
|
2001
|
+
var allBtn = upd ? "<span class=\"allbtn\"" + KBD + ">全部更新(" + upd + ")</span>" : "<span class=\"allbtn\" hidden></span>";
|
|
1883
2002
|
var body;
|
|
1884
2003
|
if (!rows.length) {
|
|
1885
2004
|
body = "<div class=\"msg\">" + (checking ? "正在读取插件清单…" : "点上方「重新检查」开始比对") + "</div>";
|
|
@@ -1903,8 +2022,8 @@ window.__ModuleLoader__.load({
|
|
|
1903
2022
|
}).join("");
|
|
1904
2023
|
}
|
|
1905
2024
|
var html = "<div class=\"hd\"><b>插件更新 · " + (busyName ? "正在更新 " + esc(busyName) + " …" : (checking ? "检查中…" : (rows.length ? upd + " 个插件有新版本" : "插件更新检查"))) + "</b>" +
|
|
1906
|
-
"<span class=\"recheck\" title=\"重新读取插件清单并比对 registry\">重新检查</span>" + allBtn +
|
|
1907
|
-
"<span class=\"x\" title=\"关闭\">×</span></div>" + restartBar() + accessModeBar() + "<div class=\"rows\">" + body + "</div>" +
|
|
2025
|
+
"<span class=\"recheck\"" + KBD + " title=\"重新读取插件清单并比对 registry\">重新检查</span>" + allBtn +
|
|
2026
|
+
"<span class=\"x\"" + KBD + " title=\"关闭\" aria-label=\"关闭\">×</span></div>" + restartBar() + accessModeBar() + "<div class=\"rows\">" + body + "</div>" +
|
|
1908
2027
|
/* 面板脚注:把「只查第三方」这条口径写在明处(plugin-manager 同款一句) */
|
|
1909
2028
|
"<div class=\"foot\">只检查第三方插件;官方 @deepseek-ai/ 包随 DSH 本体升级。</div>";
|
|
1910
2029
|
if (wrap.innerHTML !== html) {
|
|
@@ -1914,9 +2033,12 @@ window.__ModuleLoader__.load({
|
|
|
1914
2033
|
var nk = wrap.querySelector(".rows");
|
|
1915
2034
|
if (nk && st) { nk.scrollTop = st; }
|
|
1916
2035
|
}
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
2036
|
+
/* 收起状态下只更新内容,不把它弹回来 —— 显示由「打开」路径负责 */
|
|
2037
|
+
if (!panelClosed) {
|
|
2038
|
+
wrap.hidden = false;
|
|
2039
|
+
/* 位置只有一个来源:那枚「检查更新」按钮(不可拖动) */
|
|
2040
|
+
placeNear(document.getElementById(BTN_ID), wrap, PANEL_WIDTH, 220);
|
|
2041
|
+
}
|
|
1920
2042
|
}
|
|
1921
2043
|
|
|
1922
2044
|
/* ---- 列表级按钮:挂官方插件页「已安装」标题那一行的右侧 -----------------
|
|
@@ -1940,6 +2062,9 @@ window.__ModuleLoader__.load({
|
|
|
1940
2062
|
'color:var(--dsw-alias-label-primary,inherit);background:transparent;white-space:nowrap}' +
|
|
1941
2063
|
'#' + BOX_ID + ' button:hover:enabled{background:var(--dsw-alias-interactive-bg-hover,rgba(128,128,128,.14))}' +
|
|
1942
2064
|
'#' + BOX_ID + ' button:disabled{cursor:default;opacity:.55}' +
|
|
2065
|
+
/* 焦点环:这两枚是页面里自绘的 <button>,官方全局样式不会替它们画焦点,
|
|
2066
|
+
键盘用户 Tab 过来时看不见落点(面板内部同样有一条,见两处 shadow 样式) */
|
|
2067
|
+
'#' + BOX_ID + ' button:focus-visible{' + FOCUS_RING + '}' +
|
|
1943
2068
|
/* 「立即重启」用危险色:它是本插件里唯一会动进程、会打断进行中任务的按钮 */
|
|
1944
2069
|
'#' + RBTN_ID + '{color:var(--dsw-alias-label-danger,#e07070);box-shadow:inset 0 0 0 1px rgba(255,80,80,.5)}' +
|
|
1945
2070
|
'#' + RBTN_ID + ':hover:enabled{background:rgba(255,80,80,.12)}';
|
|
@@ -1987,7 +2112,7 @@ window.__ModuleLoader__.load({
|
|
|
1987
2112
|
}
|
|
1988
2113
|
|
|
1989
2114
|
mountButton(
|
|
1990
|
-
function () { paint(); if (!rows.length && !checking) { checkNow(); } },
|
|
2115
|
+
function () { panelClosed = false; paint(); if (!rows.length && !checking) { checkNow(); } },
|
|
1991
2116
|
function () { onRestartClick(); }
|
|
1992
2117
|
);
|
|
1993
2118
|
}
|
|
@@ -2077,12 +2202,305 @@ window.__ModuleLoader__.load({
|
|
|
2077
2202
|
} catch (e) {}
|
|
2078
2203
|
}
|
|
2079
2204
|
|
|
2205
|
+
/* ======================================================================
|
|
2206
|
+
* 模块 5:回合尾「本回合改动」补位卡(0.2.0 起)
|
|
2207
|
+
*
|
|
2208
|
+
* 缺陷(0.2.1-rc.2 实测):官方回合尾卡读的是 workspaceChanges 服务,摘要与
|
|
2209
|
+
* 对比只在内存 + 系统临时目录里活着,Session 一释放就 404,卡片变成一句
|
|
2210
|
+
* 「这轮改动的内容已不可用」。但每次文件工具调用都把前后全文写在工具结果事件的
|
|
2211
|
+
* meta.diffs: [{path, oldText, newText}] 里,**随会话日志永久保存** —— 客户端画
|
|
2212
|
+
* 工具卡 diff 用的就是它(dsh-client-ui-tool 的 appliedDiffs)。所以本模块
|
|
2213
|
+
* **不新增任何持久化**:用 uiConversation 的 Turn Definition 把一回合内这些
|
|
2214
|
+
* meta.diffs 聚合起来,注册到 conversation.chat.turnTail(官方声明的 list 槽位,
|
|
2215
|
+
* 已有 deliverables / plan / schedule 三家),并且**只在官方卡已经失效时才补位**。
|
|
2216
|
+
*
|
|
2217
|
+
* 边界(README 同步):
|
|
2218
|
+
* - 只覆盖**文件工具**的改动;pwsh / npm 等造成的改动进不来(只有官方卡含)。
|
|
2219
|
+
* - 嵌套调用(run_code 里的 edit)走 tool/ptc-dispatch 事件,该事件没有 turn 字段,
|
|
2220
|
+
* 用 rootCallId → turn 的映射(来自 tool/call)挂回回合。
|
|
2221
|
+
* - 探测结果只缓存在本次连接内(connection/reset 清空)。官方自己永久缓存 missing,
|
|
2222
|
+
* 那会在宿主恢复后留下双份卡。
|
|
2223
|
+
* ====================================================================== */
|
|
2224
|
+
var REACT = null;
|
|
2225
|
+
try { REACT = require("react"); } catch (e) { REACT = null; }
|
|
2226
|
+
|
|
2227
|
+
var CHANGES_KIND = "ui-tweaks-changes";
|
|
2228
|
+
var SUMMARY_ROUTE = "api/changes.summary"; /* 文档相对,勿加前导斜杠(同官方 CHANGED_FILES_ROUTE) */
|
|
2229
|
+
var DIFF_ROW_LIMIT = 400; /* 单个文件最多画多少行 diff */
|
|
2230
|
+
var LCS_LINE_LIMIT = 1200; /* 超过就退化为「全删 + 全增」,避免 DP 吃内存 */
|
|
2231
|
+
var probeCache = {}; /* sessionId:seq -> "ok" | "missing" */
|
|
2232
|
+
var rootTurn = {}; /* rootCallId -> turn(ptc 事件没有 turn) */
|
|
2233
|
+
|
|
2234
|
+
function splitLines(text) {
|
|
2235
|
+
var s = text == null ? "" : String(text);
|
|
2236
|
+
if (s === "") { return []; }
|
|
2237
|
+
if (s.charAt(s.length - 1) === "\n") { s = s.slice(0, -1); }
|
|
2238
|
+
return s.split("\n");
|
|
2239
|
+
}
|
|
2240
|
+
function naiveRows(a, b) {
|
|
2241
|
+
var rows = [], i;
|
|
2242
|
+
for (i = 0; i < a.length; i++) { rows.push({ k: "-", t: a[i] }); }
|
|
2243
|
+
for (i = 0; i < b.length; i++) { rows.push({ k: "+", t: b[i] }); }
|
|
2244
|
+
return rows;
|
|
2245
|
+
}
|
|
2246
|
+
/* 行级 LCS diff;超限或异常一律退化为「全删+全增」,绝不抛。 */
|
|
2247
|
+
function diffRows(oldText, newText) {
|
|
2248
|
+
var a = splitLines(oldText), b = splitLines(newText);
|
|
2249
|
+
if (a.length > LCS_LINE_LIMIT || b.length > LCS_LINE_LIMIT) { return naiveRows(a, b); }
|
|
2250
|
+
var n = a.length, m = b.length, w = m + 1;
|
|
2251
|
+
var dp;
|
|
2252
|
+
try { dp = new Int32Array((n + 1) * w); } catch (e) { return naiveRows(a, b); }
|
|
2253
|
+
for (var i = n - 1; i >= 0; i--) {
|
|
2254
|
+
for (var j = m - 1; j >= 0; j--) {
|
|
2255
|
+
dp[i * w + j] = a[i] === b[j]
|
|
2256
|
+
? dp[(i + 1) * w + (j + 1)] + 1
|
|
2257
|
+
: Math.max(dp[(i + 1) * w + j], dp[i * w + (j + 1)]);
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
var rows = [], x = 0, y = 0;
|
|
2261
|
+
while (x < n && y < m) {
|
|
2262
|
+
if (a[x] === b[y]) { rows.push({ k: " ", t: a[x] }); x++; y++; }
|
|
2263
|
+
else if (dp[(x + 1) * w + y] >= dp[x * w + (y + 1)]) { rows.push({ k: "-", t: a[x] }); x++; }
|
|
2264
|
+
else { rows.push({ k: "+", t: b[y] }); y++; }
|
|
2265
|
+
}
|
|
2266
|
+
while (x < n) { rows.push({ k: "-", t: a[x] }); x++; }
|
|
2267
|
+
while (y < m) { rows.push({ k: "+", t: b[y] }); y++; }
|
|
2268
|
+
return rows;
|
|
2269
|
+
}
|
|
2270
|
+
function countRows(rows) {
|
|
2271
|
+
var added = 0, deleted = 0;
|
|
2272
|
+
for (var i = 0; i < rows.length; i++) {
|
|
2273
|
+
if (rows[i].k === "+") { added++; } else if (rows[i].k === "-") { deleted++; }
|
|
2274
|
+
}
|
|
2275
|
+
return { added: added, deleted: deleted };
|
|
2276
|
+
}
|
|
2277
|
+
/* 同一文件在回合内被改多次时按 old→new 链折成一份净变化;链断返回 null(分段显示)。 */
|
|
2278
|
+
function foldParts(parts) {
|
|
2279
|
+
if (!parts || parts.length === 0) { return null; }
|
|
2280
|
+
var before = parts[0][0] == null ? "" : String(parts[0][0]);
|
|
2281
|
+
var after = parts[0][1] == null ? "" : String(parts[0][1]);
|
|
2282
|
+
for (var i = 1; i < parts.length; i++) {
|
|
2283
|
+
var o = parts[i][0];
|
|
2284
|
+
if (o != null && String(o) !== after) { return null; }
|
|
2285
|
+
if (parts[i][1] != null) { after = String(parts[i][1]); }
|
|
2286
|
+
}
|
|
2287
|
+
return { before: before, after: after };
|
|
2288
|
+
}
|
|
2289
|
+
function countParts(parts) {
|
|
2290
|
+
var added = 0, deleted = 0;
|
|
2291
|
+
for (var i = 0; i < parts.length; i++) {
|
|
2292
|
+
var c = countRows(diffRows(parts[i][0], parts[i][1]));
|
|
2293
|
+
added += c.added; deleted += c.deleted;
|
|
2294
|
+
}
|
|
2295
|
+
return { added: added, deleted: deleted };
|
|
2296
|
+
}
|
|
2297
|
+
function metaDiffs(data) {
|
|
2298
|
+
if (!data || typeof data !== "object") { return null; }
|
|
2299
|
+
var meta = data.meta;
|
|
2300
|
+
if (!meta || typeof meta !== "object") { return null; }
|
|
2301
|
+
var list = meta.diffs;
|
|
2302
|
+
if (!Array.isArray(list) || list.length === 0) { return null; }
|
|
2303
|
+
var out = [];
|
|
2304
|
+
for (var i = 0; i < list.length; i++) {
|
|
2305
|
+
var d = list[i];
|
|
2306
|
+
if (d && typeof d.path === "string" && d.path) { out.push(d); }
|
|
2307
|
+
}
|
|
2308
|
+
return out.length > 0 ? out : null;
|
|
2309
|
+
}
|
|
2310
|
+
function mergeDiff(files, diff) {
|
|
2311
|
+
var path = String(diff.path);
|
|
2312
|
+
var part = [diff.oldText == null ? null : String(diff.oldText), diff.newText == null ? "" : String(diff.newText)];
|
|
2313
|
+
for (var i = 0; i < files.length; i++) {
|
|
2314
|
+
if (files[i].path === path) {
|
|
2315
|
+
var copy = files.slice();
|
|
2316
|
+
copy[i] = { path: path, parts: files[i].parts.concat([part]) };
|
|
2317
|
+
return copy;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
return files.concat([{ path: path, parts: [part] }]);
|
|
2321
|
+
}
|
|
2322
|
+
function changesDefinition() {
|
|
2323
|
+
return {
|
|
2324
|
+
kind: CHANGES_KIND,
|
|
2325
|
+
match: function (event) {
|
|
2326
|
+
var d = (event && event.data) || {};
|
|
2327
|
+
if (event.type === "turn/start") {
|
|
2328
|
+
return d.turn === undefined ? null : { id: String(d.turn), role: "start" };
|
|
2329
|
+
}
|
|
2330
|
+
if (event.type === "tool/call") {
|
|
2331
|
+
if (d.callId !== undefined && d.turn !== undefined) { rootTurn[String(d.callId)] = d.turn; }
|
|
2332
|
+
return d.turn === undefined ? null : { id: String(d.turn), role: "update" };
|
|
2333
|
+
}
|
|
2334
|
+
if (event.type === "workspace/changes") {
|
|
2335
|
+
return d.turn === undefined ? null : { id: String(d.turn), role: "update" };
|
|
2336
|
+
}
|
|
2337
|
+
if (event.type === "tool/result") {
|
|
2338
|
+
return (d.turn !== undefined && metaDiffs(d)) ? { id: String(d.turn), role: "update" } : null;
|
|
2339
|
+
}
|
|
2340
|
+
if (event.type === "tool/ptc-dispatch") {
|
|
2341
|
+
if (!metaDiffs(d)) { return null; }
|
|
2342
|
+
var turn = rootTurn[String(d.rootCallId)];
|
|
2343
|
+
return turn === undefined ? null : { id: String(turn), role: "update" };
|
|
2344
|
+
}
|
|
2345
|
+
return null;
|
|
2346
|
+
},
|
|
2347
|
+
start: function () { return { seq: null, files: [] }; },
|
|
2348
|
+
update: function (context, match) {
|
|
2349
|
+
var prev = (context && context.state) || context || { seq: null, files: [] };
|
|
2350
|
+
var next = { seq: prev.seq == null ? null : prev.seq, files: prev.files || [] };
|
|
2351
|
+
var event = match && match.event;
|
|
2352
|
+
if (!event) { return next; }
|
|
2353
|
+
if (event.type === "workspace/changes") { return { seq: event.seq, files: next.files }; }
|
|
2354
|
+
var list = metaDiffs(event.data || {});
|
|
2355
|
+
if (!list) { return next; }
|
|
2356
|
+
var files = next.files;
|
|
2357
|
+
for (var i = 0; i < list.length; i++) { files = mergeDiff(files, list[i]); }
|
|
2358
|
+
return { seq: next.seq, files: files };
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
}
|
|
2362
|
+
function shortPath(p) {
|
|
2363
|
+
var s = String(p);
|
|
2364
|
+
return s.length > 64 ? "…" + s.slice(s.length - 63) : s;
|
|
2365
|
+
}
|
|
2366
|
+
function changesCard(props) {
|
|
2367
|
+
var owner = props || {};
|
|
2368
|
+
var state = null;
|
|
2369
|
+
try {
|
|
2370
|
+
if (owner.turn && owner.turn.data && typeof owner.turn.data.get === "function") {
|
|
2371
|
+
state = owner.turn.data.get(CHANGES_KIND);
|
|
2372
|
+
}
|
|
2373
|
+
} catch (e) { state = null; }
|
|
2374
|
+
var sessionId = owner.sessionId;
|
|
2375
|
+
var seq = state && state.seq != null ? state.seq : null;
|
|
2376
|
+
var key = (sessionId && seq != null) ? String(sessionId) + ":" + String(seq) : null;
|
|
2377
|
+
var cached = key && probeCache[key] ? probeCache[key] : null;
|
|
2378
|
+
var probeState = REACT.useState(cached || "pending");
|
|
2379
|
+
var probe = probeState[0], setProbe = probeState[1];
|
|
2380
|
+
var expState = REACT.useState(false);
|
|
2381
|
+
var expanded = expState[0], setExpanded = expState[1];
|
|
2382
|
+
REACT.useEffect(function () {
|
|
2383
|
+
if (!key || cached) { return undefined; }
|
|
2384
|
+
var cancelled = false;
|
|
2385
|
+
try {
|
|
2386
|
+
fetch(SUMMARY_ROUTE + "?" + new URLSearchParams({ sessionId: String(sessionId), seq: String(seq) }),
|
|
2387
|
+
{ cache: "no-store", credentials: "same-origin" }).then(function (res) {
|
|
2388
|
+
if (cancelled) { return; }
|
|
2389
|
+
var v = res.status === 200 ? "ok" : (res.status === 404 ? "missing" : "error");
|
|
2390
|
+
if (v !== "error" && probeCache[key] === undefined) { probeCache[key] = v; }
|
|
2391
|
+
setProbe(v);
|
|
2392
|
+
}).catch(function () {});
|
|
2393
|
+
} catch (e) {}
|
|
2394
|
+
return function () { cancelled = true; };
|
|
2395
|
+
}, [key, cached]);
|
|
2396
|
+
/* 只在「官方已失效」时才补位:ok / pending / error 一律不画,避免出现双份卡。 */
|
|
2397
|
+
if (!state || !state.files || state.files.length === 0 || seq == null || probe !== "missing") { return null; }
|
|
2398
|
+
|
|
2399
|
+
var files = state.files, totals = { added: 0, deleted: 0 }, prepared = [], i, j;
|
|
2400
|
+
for (i = 0; i < files.length; i++) {
|
|
2401
|
+
var net = foldParts(files[i].parts);
|
|
2402
|
+
var rows = (expanded && net !== null) ? diffRows(net.before, net.after) : null;
|
|
2403
|
+
var counted = net !== null ? countRows(diffRows(net.before, net.after)) : countParts(files[i].parts);
|
|
2404
|
+
totals.added += counted.added; totals.deleted += counted.deleted;
|
|
2405
|
+
prepared.push({ path: files[i].path, parts: files[i].parts, added: counted.added, deleted: counted.deleted, rows: rows });
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
var mono = "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace";
|
|
2409
|
+
var dim = { opacity: 0.6, fontSize: 11 };
|
|
2410
|
+
var add = { color: "#3fb950" };
|
|
2411
|
+
var del = { color: "#f85149" };
|
|
2412
|
+
var children = [];
|
|
2413
|
+
children.push(REACT.createElement("div", {
|
|
2414
|
+
key: "head",
|
|
2415
|
+
style: { display: "flex", alignItems: "center", gap: 8, cursor: "pointer", userSelect: "none" },
|
|
2416
|
+
onClick: function () { setExpanded(!expanded); }
|
|
2417
|
+
},
|
|
2418
|
+
REACT.createElement("span", { style: { fontWeight: 600 } }, "本回合改动"),
|
|
2419
|
+
REACT.createElement("span", { style: dim }, files.length + " 个文件"),
|
|
2420
|
+
REACT.createElement("span", { style: add }, "+" + totals.added),
|
|
2421
|
+
REACT.createElement("span", { style: del }, "−" + totals.deleted),
|
|
2422
|
+
REACT.createElement("span", { style: { marginLeft: "auto", opacity: 0.6, fontSize: 11 } }, expanded ? "收起" : "展开")
|
|
2423
|
+
));
|
|
2424
|
+
children.push(REACT.createElement("div", { key: "note", style: { opacity: 0.5, fontSize: 11, marginTop: 2 } },
|
|
2425
|
+
"(据会话日志重建 · 仅文件工具的改动)"));
|
|
2426
|
+
if (expanded) {
|
|
2427
|
+
for (i = 0; i < prepared.length; i++) {
|
|
2428
|
+
var f = prepared[i];
|
|
2429
|
+
var block = [];
|
|
2430
|
+
block.push(REACT.createElement("div", {
|
|
2431
|
+
key: "p", style: { display: "flex", alignItems: "center", gap: 6, marginTop: 6 }
|
|
2432
|
+
},
|
|
2433
|
+
REACT.createElement("span", { style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", fontFamily: mono }, title: f.path }, shortPath(f.path)),
|
|
2434
|
+
REACT.createElement("span", { style: add }, "+" + f.added),
|
|
2435
|
+
REACT.createElement("span", { style: del }, "−" + f.deleted)
|
|
2436
|
+
));
|
|
2437
|
+
if (f.rows) {
|
|
2438
|
+
var shown = f.rows.length > DIFF_ROW_LIMIT ? f.rows.slice(0, DIFF_ROW_LIMIT) : f.rows;
|
|
2439
|
+
for (j = 0; j < shown.length; j++) {
|
|
2440
|
+
block.push(REACT.createElement("div", {
|
|
2441
|
+
key: "r" + j,
|
|
2442
|
+
style: { fontFamily: mono, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all",
|
|
2443
|
+
color: shown[j].k === "+" ? "#3fb950" : (shown[j].k === "-" ? "#f85149" : "inherit"),
|
|
2444
|
+
opacity: shown[j].k === " " ? 0.7 : 1 }
|
|
2445
|
+
}, (shown[j].k === " " ? " " : shown[j].k + " ") + shown[j].t));
|
|
2446
|
+
}
|
|
2447
|
+
if (f.rows.length > DIFF_ROW_LIMIT) {
|
|
2448
|
+
block.push(REACT.createElement("div", { key: "more", style: dim }, "…还有 " + (f.rows.length - DIFF_ROW_LIMIT) + " 行"));
|
|
2449
|
+
}
|
|
2450
|
+
} else {
|
|
2451
|
+
for (j = 0; j < f.parts.length; j++) {
|
|
2452
|
+
block.push(REACT.createElement("div", { key: "seg" + j, style: { opacity: 0.6, fontSize: 11, marginTop: 4 } },
|
|
2453
|
+
"第 " + (j + 1) + " 段改动(与前一段不衔接,分段显示)"));
|
|
2454
|
+
var prows = diffRows(f.parts[j][0], f.parts[j][1]);
|
|
2455
|
+
var pshown = prows.length > DIFF_ROW_LIMIT ? prows.slice(0, DIFF_ROW_LIMIT) : prows;
|
|
2456
|
+
for (var r2 = 0; r2 < pshown.length; r2++) {
|
|
2457
|
+
block.push(REACT.createElement("div", {
|
|
2458
|
+
key: "pr" + j + "_" + r2,
|
|
2459
|
+
style: { fontFamily: mono, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all",
|
|
2460
|
+
color: pshown[r2].k === "+" ? "#3fb950" : (pshown[r2].k === "-" ? "#f85149" : "inherit"),
|
|
2461
|
+
opacity: pshown[r2].k === " " ? 0.7 : 1 }
|
|
2462
|
+
}, (pshown[r2].k === " " ? " " : pshown[r2].k + " ") + pshown[r2].t));
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
children.push(REACT.createElement("div", { key: "f" + i }, block));
|
|
2467
|
+
}
|
|
2468
|
+
}
|
|
2469
|
+
return REACT.createElement("div", {
|
|
2470
|
+
style: { marginTop: 8, border: "1px solid var(--dsw-alias-border-neutral-secondary, rgba(128,128,128,0.25))",
|
|
2471
|
+
borderRadius: 8, padding: "8px 10px", fontSize: 12 }
|
|
2472
|
+
}, children);
|
|
2473
|
+
}
|
|
2474
|
+
function installChangesCard(scope, ctx) {
|
|
2475
|
+
if (!REACT) { throw new Error("ui-tweaks: require(\"react\") 不可用,跳过改动补位卡"); }
|
|
2476
|
+
var s = scope || ctx || {};
|
|
2477
|
+
var conv = s.uiConversation || (ctx && ctx.uiConversation) || null;
|
|
2478
|
+
var slots = s.slots || (ctx && ctx.slots) || null;
|
|
2479
|
+
if (!conv || !conv.events || typeof conv.events.register !== "function") {
|
|
2480
|
+
throw new Error("ui-tweaks: uiConversation.events 不可用");
|
|
2481
|
+
}
|
|
2482
|
+
if (!slots || typeof slots.register !== "function" || typeof slots.inject !== "function") {
|
|
2483
|
+
throw new Error("ui-tweaks: slots 服务不可用");
|
|
2484
|
+
}
|
|
2485
|
+
conv.events.register(changesDefinition());
|
|
2486
|
+
slots.inject("conversation.chat.turnTail", function () {
|
|
2487
|
+
return slots.register({
|
|
2488
|
+
name: "conversation.chat.turnTail",
|
|
2489
|
+
id: CHANGES_KIND,
|
|
2490
|
+
order: 30,
|
|
2491
|
+
inject: function (sessionId) { return { sessionId: sessionId }; }
|
|
2492
|
+
}, changesCard);
|
|
2493
|
+
});
|
|
2494
|
+
try { ctx.on("connection/reset", function () { probeCache = {}; rootTurn = {}; }); } catch (e) {}
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2080
2497
|
function apply(ctx) {
|
|
2081
2498
|
try { window.__DSHUIW_BUILD__ = BUILD; } catch (e) {}
|
|
2082
2499
|
guard("tablefix", installTableWidthFix);
|
|
2083
2500
|
guard("scrollbar", installLeftScrollbar);
|
|
2084
2501
|
guard("updates", function () { installPluginUpdates(ctx); });
|
|
2085
2502
|
guard("anim", installAnimationFix);
|
|
2503
|
+
guard("changes", function () { ctx.inject(["uiConversation", "slots"], function (s) { installChangesCard(s, ctx); }); });
|
|
2086
2504
|
}
|
|
2087
2505
|
|
|
2088
2506
|
module.exports = { name: "ui-tweaks", apply: apply, inject: ["remote", "remote.pluginManager", "remote.pluginInventory"] };
|