@mrrisega/dsh-remote 0.6.7 → 0.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -259,8 +259,9 @@ html[data-dsh-ma-fs="3"] { --dsh-ma-fs-scale: 1.32; -webkit-text-size-adjust: 13
|
|
|
259
259
|
用户至少还能从这里"返回设备列表"自救(这是 2026-09-19 死局的兜底出口)。 */
|
|
260
260
|
button.dsh-ma-fab {
|
|
261
261
|
position: fixed;
|
|
262
|
-
|
|
263
|
-
|
|
262
|
+
/* 位置由 JS 计算后写 inline left/top(默认右上角,可拖动;见 SCRIPT 里的 fabPos);
|
|
263
|
+
CSS 只给尺寸/层级/指针语义 —— 用 left/top 而不是 right/bottom,拖动才不会被"贴边"钉住。 */
|
|
264
|
+
left: auto; top: auto; right: 10px; bottom: auto;
|
|
264
265
|
z-index: 400;
|
|
265
266
|
width: 46px; height: 46px; padding: 0;
|
|
266
267
|
display: flex; align-items: center; justify-content: center;
|
|
@@ -270,10 +271,12 @@ button.dsh-ma-fab {
|
|
|
270
271
|
border: .5px solid var(--dsw-alias-border-l3, rgba(127,127,127,.3));
|
|
271
272
|
box-shadow: 0 2px 12px rgba(0,0,0,.2);
|
|
272
273
|
color: var(--dsw-alias-label-primary, #111);
|
|
273
|
-
cursor:
|
|
274
|
+
cursor: grab; touch-action: none; /* 触屏拖动不被页面滚动抢走 */
|
|
275
|
+
user-select: none; -webkit-user-select: none;
|
|
274
276
|
-webkit-tap-highlight-color: transparent;
|
|
275
277
|
}
|
|
276
278
|
button.dsh-ma-fab:active { transform: scale(.94); }
|
|
279
|
+
button.dsh-ma-fab.dsh-ma-fab-dragging { cursor: grabbing; transform: scale(1.06); }
|
|
277
280
|
button.dsh-ma-fab .dsh-ma-fab-lock {
|
|
278
281
|
position: absolute; top: -3px; right: -3px;
|
|
279
282
|
font-size: 12px; line-height: 1; padding: 2px 3px; border-radius: 999px;
|
|
@@ -283,6 +286,7 @@ button.dsh-ma-fab .dsh-ma-fab-lock {
|
|
|
283
286
|
/* 菜单卡片:fixed 贴右下,最大高度受限可滚动 */
|
|
284
287
|
div.dsh-ma-menu {
|
|
285
288
|
position: fixed;
|
|
289
|
+
/* 打开时按悬浮按钮的位置重算(按钮在上半屏→菜单挂下方;靠左→左对齐),见 placeMenu() */
|
|
286
290
|
right: max(10px, env(safe-area-inset-right));
|
|
287
291
|
bottom: calc(70px + env(safe-area-inset-bottom));
|
|
288
292
|
z-index: 401;
|
|
@@ -688,11 +692,64 @@ const SCRIPT = `(() => {
|
|
|
688
692
|
'<div class="dsh-ma-menu-note">返回后可重新选择设备;端到端加密需在设备列表页重新解锁(会话密钥只存在本机内存里,刷新即失效)。</div>';
|
|
689
693
|
document.body.appendChild(menu);
|
|
690
694
|
|
|
695
|
+
/* ---- 悬浮按钮的位置:默认**右上角**(原来在右下角,正好压住输入框的发送按钮);
|
|
696
|
+
可拖动,位置按"最近的角 + 距边偏移"记忆 —— 旋转屏幕/换窗口宽度都不会跑到屏幕外。 ---- */
|
|
697
|
+
const FAB_KEY = "dsh-ma-fab-pos";
|
|
698
|
+
const FAB_SIZE = 46, FAB_EDGE = 10;
|
|
699
|
+
const readFabPos = () => {
|
|
700
|
+
try {
|
|
701
|
+
const raw = window.localStorage && window.localStorage.getItem(FAB_KEY);
|
|
702
|
+
const p = raw ? JSON.parse(raw) : null;
|
|
703
|
+
if (p && (p.h === "left" || p.h === "right") && (p.v === "top" || p.v === "bottom")
|
|
704
|
+
&& typeof p.dx === "number" && typeof p.dy === "number") return p;
|
|
705
|
+
} catch (e) { /* 忽略 */ }
|
|
706
|
+
return null;
|
|
707
|
+
};
|
|
708
|
+
const defaultFabPos = () => ({ h: "right", v: "top", dx: FAB_EDGE, dy: FAB_EDGE });
|
|
709
|
+
let fabPos = readFabPos() || defaultFabPos();
|
|
710
|
+
const fabXY = (pos) => {
|
|
711
|
+
const vw = window.innerWidth || 360, vh = window.innerHeight || 640;
|
|
712
|
+
return {
|
|
713
|
+
left: pos.h === "left" ? pos.dx : vw - FAB_SIZE - pos.dx,
|
|
714
|
+
top: pos.v === "top" ? pos.dy : vh - FAB_SIZE - pos.dy,
|
|
715
|
+
};
|
|
716
|
+
};
|
|
717
|
+
const clampFabXY = (xy) => {
|
|
718
|
+
const vw = window.innerWidth || 360, vh = window.innerHeight || 640;
|
|
719
|
+
return {
|
|
720
|
+
left: Math.max(4, Math.min(xy.left, vw - FAB_SIZE - 4)),
|
|
721
|
+
top: Math.max(4, Math.min(xy.top, vh - FAB_SIZE - 4)),
|
|
722
|
+
};
|
|
723
|
+
};
|
|
724
|
+
const applyFabPos = () => {
|
|
725
|
+
const xy = clampFabXY(fabXY(fabPos));
|
|
726
|
+
fab.style.left = Math.round(xy.left) + "px";
|
|
727
|
+
fab.style.top = Math.round(xy.top) + "px";
|
|
728
|
+
return xy;
|
|
729
|
+
};
|
|
730
|
+
const saveFabPos = () => {
|
|
731
|
+
try { window.localStorage && window.localStorage.setItem(FAB_KEY, JSON.stringify(fabPos)); } catch (e) { /* 忽略 */ }
|
|
732
|
+
};
|
|
733
|
+
/** 拖动落点 → "最近的角 + 偏移"(保留用户的意图,同时保证换尺寸后仍在屏内)。 */
|
|
734
|
+
const fabPosFromXY = (xy) => {
|
|
735
|
+
const vw = window.innerWidth || 360, vh = window.innerHeight || 640;
|
|
736
|
+
const h = xy.left + FAB_SIZE / 2 < vw / 2 ? "left" : "right";
|
|
737
|
+
const v = xy.top + FAB_SIZE / 2 < vh / 2 ? "top" : "bottom";
|
|
738
|
+
return {
|
|
739
|
+
h: h, v: v,
|
|
740
|
+
dx: h === "left" ? Math.max(0, xy.left) : Math.max(0, vw - FAB_SIZE - xy.left),
|
|
741
|
+
dy: v === "top" ? Math.max(0, xy.top) : Math.max(0, vh - FAB_SIZE - xy.top),
|
|
742
|
+
};
|
|
743
|
+
};
|
|
744
|
+
|
|
691
745
|
const fab = document.createElement("button");
|
|
692
746
|
fab.type = "button";
|
|
693
747
|
fab.className = "dsh-ma-fab";
|
|
694
|
-
fab.setAttribute("aria-label", "
|
|
748
|
+
fab.setAttribute("aria-label", "远程控制菜单(可拖动)");
|
|
749
|
+
fab.title = "远程控制:字号 / 加密状态 / 返回设备列表(可拖动移动位置)";
|
|
695
750
|
fab.innerHTML = '<span class="dsh-ma-fab-ico">字</span><span class="dsh-ma-fab-lock" data-role="lock">🔒</span>';
|
|
751
|
+
fab.style.left = Math.round(clampFabXY(fabXY(fabPos)).left) + "px";
|
|
752
|
+
fab.style.top = Math.round(clampFabXY(fabXY(fabPos)).top) + "px";
|
|
696
753
|
document.body.appendChild(fab);
|
|
697
754
|
|
|
698
755
|
const fsRange = menu.querySelector('input[type="range"]');
|
|
@@ -743,13 +800,80 @@ const SCRIPT = `(() => {
|
|
|
743
800
|
[600, 2000, 5000].forEach((ms) => setTimeout(renderE2ee, ms)); // shim 可能晚于本层挂牌
|
|
744
801
|
|
|
745
802
|
/* ---- 交互 ---- */
|
|
803
|
+
/* 菜单跟随按钮:按钮在上半屏 → 菜单挂在它下方;按钮靠左 → 菜单左对齐(都不超出视口)。 */
|
|
804
|
+
const placeMenu = () => {
|
|
805
|
+
try {
|
|
806
|
+
const r = fab.getBoundingClientRect();
|
|
807
|
+
const vw = window.innerWidth || 360, vh = window.innerHeight || 640;
|
|
808
|
+
const mr = menu.getBoundingClientRect();
|
|
809
|
+
const mw = mr.width || Math.min(vw * 0.86, 320);
|
|
810
|
+
const mh = mr.height || 300;
|
|
811
|
+
const GAP = 8, EDGE = 8;
|
|
812
|
+
menu.style.top = "auto"; menu.style.bottom = "auto";
|
|
813
|
+
if (r.top + r.height / 2 < vh / 2) {
|
|
814
|
+
menu.style.top = Math.round(Math.max(EDGE, Math.min(r.bottom + GAP, vh - mh - EDGE))) + "px";
|
|
815
|
+
} else {
|
|
816
|
+
menu.style.bottom = Math.round(Math.max(EDGE, vh - r.top + GAP)) + "px";
|
|
817
|
+
}
|
|
818
|
+
menu.style.left = "auto"; menu.style.right = "auto";
|
|
819
|
+
if (r.left + r.width / 2 < vw / 2) {
|
|
820
|
+
menu.style.left = Math.round(Math.max(EDGE, Math.min(r.left, vw - mw - EDGE))) + "px";
|
|
821
|
+
} else {
|
|
822
|
+
menu.style.right = Math.round(Math.max(EDGE, Math.min(vw - r.right, vw - mw - EDGE))) + "px";
|
|
823
|
+
}
|
|
824
|
+
} catch (e) { /* 定位失败不影响功能(退回 CSS 默认位置) */ }
|
|
825
|
+
};
|
|
746
826
|
const closeMenu = () => { menu.hidden = true; };
|
|
747
|
-
const openMenu = () => { renderE2ee(); menu.hidden = false; };
|
|
827
|
+
const openMenu = () => { renderE2ee(); menu.hidden = false; placeMenu(); };
|
|
828
|
+
let suppressClickUntil = 0; // 拖完的那一下不要当成"点击打开菜单"
|
|
829
|
+
let tapHandledAt = 0; // 轻点已在 pointerup 里处理过(避免 click 再翻一次)
|
|
830
|
+
const toggleMenu = () => { if (menu.hidden) openMenu(); else closeMenu(); };
|
|
748
831
|
fab.addEventListener("click", (e) => {
|
|
749
832
|
e.stopPropagation(); e.preventDefault();
|
|
750
|
-
if (
|
|
833
|
+
if (Date.now() < suppressClickUntil) return; // 刚拖完 → 不算点击
|
|
834
|
+
if (Date.now() - tapHandledAt < 800) return; // 轻点已在 pointerup 处理 → 不重复开合
|
|
835
|
+
toggleMenu(); // 键盘/老浏览器兜底
|
|
751
836
|
});
|
|
752
837
|
menu.addEventListener("click", (e) => e.stopPropagation());
|
|
838
|
+
|
|
839
|
+
/* ---- 拖动:pointer 事件 + 指针捕获(手指拖出按钮范围也不断线);拖动阈值 6px 区分"点击"与"拖动" ---- */
|
|
840
|
+
let fabDrag = null;
|
|
841
|
+
fab.addEventListener("pointerdown", (e) => {
|
|
842
|
+
if (e.button !== undefined && e.button !== 0) return; // 只认左键/单指
|
|
843
|
+
const xy = clampFabXY(fabXY(fabPos));
|
|
844
|
+
fabDrag = { id: e.pointerId, sx: e.clientX, sy: e.clientY, ox: xy.left, oy: xy.top, moved: false };
|
|
845
|
+
try { if (fab.setPointerCapture) fab.setPointerCapture(e.pointerId); } catch (e2) { /* 老浏览器忽略 */ }
|
|
846
|
+
// ⚠️ 这里**不能** preventDefault:实测它会连带掐掉随后的 click 事件(Chrome 触摸语义),
|
|
847
|
+
// 结果是"按钮点了没反应"。防滚动交给 CSS 的 touch-action:none,轻点交给 pointerup 处理。
|
|
848
|
+
});
|
|
849
|
+
fab.addEventListener("pointermove", (e) => {
|
|
850
|
+
if (!fabDrag || e.pointerId !== fabDrag.id) return;
|
|
851
|
+
const dx = e.clientX - fabDrag.sx, dy = e.clientY - fabDrag.sy;
|
|
852
|
+
if (!fabDrag.moved && Math.abs(dx) + Math.abs(dy) < 6) return; // 阈值内视为点击(细微抖动不挪位)
|
|
853
|
+
if (!fabDrag.moved) { fabDrag.moved = true; fab.classList.add("dsh-ma-fab-dragging"); closeMenu(); }
|
|
854
|
+
const xy = clampFabXY({ left: fabDrag.ox + dx, top: fabDrag.oy + dy });
|
|
855
|
+
fab.style.left = Math.round(xy.left) + "px";
|
|
856
|
+
fab.style.top = Math.round(xy.top) + "px";
|
|
857
|
+
});
|
|
858
|
+
const endFabDrag = (e) => {
|
|
859
|
+
if (!fabDrag || (e && e.pointerId !== undefined && e.pointerId !== fabDrag.id)) return;
|
|
860
|
+
const moved = fabDrag.moved;
|
|
861
|
+
fabDrag = null;
|
|
862
|
+
try { fab.classList.remove("dsh-ma-fab-dragging"); } catch (e2) { /* 忽略 */ }
|
|
863
|
+
if (!moved) {
|
|
864
|
+
// 轻点(没超过拖动阈值)→ 在这里开合菜单:pointerdown 不再 preventDefault 之后,
|
|
865
|
+
// 触摸场景的 click 并不总是可靠地跟上来,pointerup 才是稳的那个点。
|
|
866
|
+
tapHandledAt = Date.now();
|
|
867
|
+
toggleMenu();
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
fabPos = fabPosFromXY(clampFabXY({ left: parseFloat(fab.style.left) || 0, top: parseFloat(fab.style.top) || 0 }));
|
|
871
|
+
saveFabPos();
|
|
872
|
+
suppressClickUntil = Date.now() + 400; // 拖动结束后的 click 事件不算"点开菜单"
|
|
873
|
+
};
|
|
874
|
+
fab.addEventListener("pointerup", endFabDrag);
|
|
875
|
+
fab.addEventListener("pointercancel", endFabDrag);
|
|
876
|
+
fab.addEventListener("lostpointercapture", endFabDrag);
|
|
753
877
|
if (fsRange) {
|
|
754
878
|
fsRange.addEventListener("input", (e) => { e.stopPropagation(); setLevel(Number(fsRange.value), true); });
|
|
755
879
|
fsRange.addEventListener("click", (e) => e.stopPropagation());
|
|
@@ -770,6 +894,10 @@ const SCRIPT = `(() => {
|
|
|
770
894
|
if (t === fab || (t && t.closest && t.closest(".dsh-ma-menu, .dsh-ma-fab"))) return;
|
|
771
895
|
closeMenu();
|
|
772
896
|
});
|
|
897
|
+
try {
|
|
898
|
+
window.addEventListener("resize", () => { applyFabPos(); if (!menu.hidden) placeMenu(); }, { passive: true });
|
|
899
|
+
window.addEventListener("orientationchange", () => { applyFabPos(); if (!menu.hidden) placeMenu(); }, { passive: true });
|
|
900
|
+
} catch (e9) { /* 忽略 */ }
|
|
773
901
|
const devEl = menu.querySelector(".dsh-ma-menu-dev");
|
|
774
902
|
if (devEl) {
|
|
775
903
|
const d = deviceIdOfPath();
|
|
@@ -362,3 +362,80 @@ test("多端适配:悬浮按钮与字号样式的 CSS 必须在 @media(max-wid
|
|
|
362
362
|
assert.match(out, /html\[data-dsh-ma-fs="3"\]/, "字号档位样式也应在媒体查询之外(电脑端同样生效)");
|
|
363
363
|
assert.match(out, /z-index:\s*40[01]/, "悬浮按钮/菜单层级要高于遮罩(290),遮罩异常时仍是自救出口");
|
|
364
364
|
});
|
|
365
|
+
|
|
366
|
+
// ── 悬浮按钮位置:默认右上角 + 可拖动(2026-09-19 用户反馈) ──────────────────────
|
|
367
|
+
// 现场:按钮原来固定在右下角,**正好压住输入框的发送按钮**;用户要求默认挪到右上角并支持拖动。
|
|
368
|
+
|
|
369
|
+
test("悬浮按钮:默认在右上角(不再压住输入区的发送按钮)", () => {
|
|
370
|
+
const env = runAdapter(harness({ width: 390 }));
|
|
371
|
+
const fab = findFab(env);
|
|
372
|
+
assert.ok(fab, "悬浮按钮应存在");
|
|
373
|
+
// 默认 = 距右上角各 10px:left = 390 - 46 - 10 = 334,top = 10
|
|
374
|
+
assert.equal(fab.style.top, "10px", "默认应贴右上角(top 小)");
|
|
375
|
+
assert.equal(fab.style.left, "334px", "默认应贴右上角(left 靠右)");
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
test("悬浮按钮:拖动后位置改变并持久化,且拖完那一下不算点击", () => {
|
|
379
|
+
const env = runAdapter(harness({ width: 390 }));
|
|
380
|
+
const fab = findFab(env);
|
|
381
|
+
const menu = findMenu(env);
|
|
382
|
+
// 起始为默认右上角 (334,10);按下点 (340,20) → 拖到 (140,420):位移 -200/+400
|
|
383
|
+
fab.dispatch("pointerdown", { pointerId: 7, clientX: 340, clientY: 20 });
|
|
384
|
+
fab.dispatch("pointermove", { pointerId: 7, clientX: 140, clientY: 420 }); // 位移远超 6px 阈值
|
|
385
|
+
fab.dispatch("pointerup", { pointerId: 7, clientX: 140, clientY: 420 });
|
|
386
|
+
assert.equal(fab.style.left, "134px", "按手指位移平移(抓住哪就从哪拖,不瞬移到指尖)");
|
|
387
|
+
assert.equal(fab.style.top, "410px", "按手指位移平移");
|
|
388
|
+
const saved = JSON.parse(env.win.localStorage.getItem("dsh-ma-fab-pos") || "null");
|
|
389
|
+
assert.ok(saved && saved.h && saved.v, "位置应被持久化(按最近的角 + 偏移)");
|
|
390
|
+
assert.equal(saved.h, "left", "落点在左半屏 → 记成左锚");
|
|
391
|
+
assert.equal(saved.v, "bottom", "落点在下半屏 → 记成下锚");
|
|
392
|
+
assert.equal(saved.dx, 134, "左锚的偏移 = 距左边距离");
|
|
393
|
+
assert.equal(saved.dy, 344, "下锚的偏移 = 距下边距离(800-46-410)");
|
|
394
|
+
// 拖完紧接着的 click 不应打开菜单(否则拖到一半就弹菜单,很难用)
|
|
395
|
+
fab.dispatch("click");
|
|
396
|
+
assert.equal(menu.hidden, true, "拖动结束后的 click 不得当作点击打开菜单");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("悬浮按钮:轻点(pointerdown+pointerup 不移动)开合菜单 —— 触屏的主路径", () => {
|
|
400
|
+
const env = runAdapter(harness({ width: 390 }));
|
|
401
|
+
const fab = findFab(env);
|
|
402
|
+
const menu = findMenu(env);
|
|
403
|
+
assert.equal(menu.hidden, true, "初始应收起");
|
|
404
|
+
// 触摸路径:pointerdown → pointerup(无位移)→ 菜单应当开;不能再依赖 click(曾被 preventDefault 掐掉)
|
|
405
|
+
fab.dispatch("pointerdown", { pointerId: 3, clientX: 340, clientY: 30 });
|
|
406
|
+
fab.dispatch("pointerup", { pointerId: 3, clientX: 340, clientY: 30 });
|
|
407
|
+
assert.equal(menu.hidden, false, "轻点应打开菜单");
|
|
408
|
+
// 再轻点一次 → 收起
|
|
409
|
+
fab.dispatch("pointerdown", { pointerId: 4, clientX: 340, clientY: 30 });
|
|
410
|
+
fab.dispatch("pointerup", { pointerId: 4, clientX: 340, clientY: 30 });
|
|
411
|
+
assert.equal(menu.hidden, true, "再轻点应收起");
|
|
412
|
+
// 位置不应被"轻点"改动
|
|
413
|
+
assert.equal(fab.style.left, "334px", "轻点不得移动按钮");
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
test("悬浮按钮:位置跨会话记忆(读 localStorage),越界会被夹回视口内", () => {
|
|
417
|
+
const env = harness({ width: 390 });
|
|
418
|
+
env.win.localStorage.setItem("dsh-ma-fab-pos", JSON.stringify({ h: "left", v: "top", dx: 12, dy: 34 }));
|
|
419
|
+
runAdapter(env);
|
|
420
|
+
const fab = findFab(env);
|
|
421
|
+
assert.equal(fab.style.left, "12px", "左锚 → 用 dx");
|
|
422
|
+
assert.equal(fab.style.top, "34px", "上锚 → 用 dy");
|
|
423
|
+
|
|
424
|
+
// 越界(dx 巨大)→ 夹回屏内:left ≤ 390-46-4
|
|
425
|
+
const env2 = harness({ width: 390 });
|
|
426
|
+
env2.win.localStorage.setItem("dsh-ma-fab-pos", JSON.stringify({ h: "left", v: "top", dx: 9999, dy: 0 }));
|
|
427
|
+
runAdapter(env2);
|
|
428
|
+
assert.equal(findFab(env2).style.left, "340px", "越界位置必须被夹进视口(不能跑到屏幕外)");
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("悬浮按钮:菜单跟随按钮位置(按钮在上半屏 → 菜单挂下方;靠左 → 左对齐)", () => {
|
|
432
|
+
const env = runAdapter(harness({ width: 390 }));
|
|
433
|
+
const fab = findFab(env);
|
|
434
|
+
const menu = findMenu(env);
|
|
435
|
+
fab._rect = { left: 20, top: 12, right: 66, bottom: 58, width: 46, height: 46 }; // 左上角
|
|
436
|
+
fab.dispatch("click");
|
|
437
|
+
assert.equal(menu.hidden, false, "点击应打开菜单");
|
|
438
|
+
assert.equal(menu.style.top, "66px", "按钮在上半屏 → 菜单挂在按钮下方(bottom+8)");
|
|
439
|
+
assert.equal(menu.style.left, "20px", "按钮靠左 → 菜单左对齐到按钮");
|
|
440
|
+
assert.equal(menu.style.right, "auto", "左对齐时不应残留 right");
|
|
441
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrrisega/dsh-remote",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "手机远程控制 DeepSeek Harness · Remote control DeepSeek Harness (dsh web) from any phone browser — 100% 全功能 App 级体验:发消息、看工具执行、审批权限、改设置、管凭据,含特权操作,免内网穿透。一条命令安装 npx @mrrisega/dsh-remote。Mobile remote control for DSH, self-host or SaaS, no server needed on LAN.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"deepseek-harness",
|
|
@@ -2379,7 +2379,7 @@ const PLUGIN_ID = "dsh-remote-web";
|
|
|
2379
2379
|
const PLUGIN_LEGACY_IDS = ["dsh-remote-ui"];
|
|
2380
2380
|
const PLUGIN_ALL_IDS = [PLUGIN_ID, ...PLUGIN_LEGACY_IDS];
|
|
2381
2381
|
/** 插件自身发布版本(与 dsh-remote 根包同步递增)。 */
|
|
2382
|
-
const PLUGIN_VERSION = "0.6.
|
|
2382
|
+
const PLUGIN_VERSION = "0.6.8";
|
|
2383
2383
|
const UPDATE_LOG = ".dsh-update.log";
|
|
2384
2384
|
const UPDATE_MARKER = ".dsh-update-running";
|
|
2385
2385
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-remote-web",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.8",
|
|
4
4
|
"description": "公网远程控制 DeepSeek Harness(dsh web):安装即得专属加密地址,人在外面也能用手机访问电脑上的 dsh——无需同一局域网/WiFi、无需公网 IP 与内网穿透,全程加密;手机端 100% 还原电脑体验(对话/工具/审批/设置)。技术用户可选自建服务,流量走自己的服务器。Remote control DeepSeek Harness (dsh web) from anywhere over the public internet — install, get an encrypted URL, use it from your phone on any network (dual-half cordis plugin: Settings panel + same-origin /dsh-remote routes). (2026-09 由 dsh-remote-ui 更名 / renamed from dsh-remote-ui; dsh-remote 的 dsh web 插件半,不是纯 UI/皮肤插件)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|