@xcanwin/manyoyo 5.2.15 → 5.2.23
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/lib/plugin/playwright.js +7 -1
- package/lib/web/frontend/app.css +52 -47
- package/lib/web/frontend/app.html +22 -8
- package/lib/web/frontend/app.js +104 -9
- package/lib/web/server.js +7 -8
- package/package.json +1 -1
package/lib/plugin/playwright.js
CHANGED
|
@@ -676,6 +676,7 @@ class PlaywrightPlugin {
|
|
|
676
676
|
}
|
|
677
677
|
|
|
678
678
|
return {
|
|
679
|
+
outputDir: '/tmp/playwright-mcp',
|
|
679
680
|
server: {
|
|
680
681
|
host: def.listenHost,
|
|
681
682
|
port,
|
|
@@ -1339,13 +1340,18 @@ class PlaywrightPlugin {
|
|
|
1339
1340
|
const scenes = this.resolveTargets('all');
|
|
1340
1341
|
for (const sceneName of scenes) {
|
|
1341
1342
|
const url = `http://${host}:${this.scenePort(sceneName)}/mcp`;
|
|
1342
|
-
this.writeStdout(`claude mcp add
|
|
1343
|
+
this.writeStdout(`claude mcp add -t http -s user playwright-${sceneName} ${url}`);
|
|
1343
1344
|
}
|
|
1344
1345
|
this.writeStdout('');
|
|
1345
1346
|
for (const sceneName of scenes) {
|
|
1346
1347
|
const url = `http://${host}:${this.scenePort(sceneName)}/mcp`;
|
|
1347
1348
|
this.writeStdout(`codex mcp add playwright-${sceneName} --url ${url}`);
|
|
1348
1349
|
}
|
|
1350
|
+
this.writeStdout('');
|
|
1351
|
+
for (const sceneName of scenes) {
|
|
1352
|
+
const url = `http://${host}:${this.scenePort(sceneName)}/mcp`;
|
|
1353
|
+
this.writeStdout(`gemini mcp add -t http -s user playwright-${sceneName} ${url}`);
|
|
1354
|
+
}
|
|
1349
1355
|
|
|
1350
1356
|
return 0;
|
|
1351
1357
|
}
|
package/lib/web/frontend/app.css
CHANGED
|
@@ -558,7 +558,7 @@ textarea:focus-visible {
|
|
|
558
558
|
min-height: 0;
|
|
559
559
|
padding: 14px;
|
|
560
560
|
display: grid;
|
|
561
|
-
grid-template-rows: auto
|
|
561
|
+
grid-template-rows: auto minmax(0, 1fr) auto;
|
|
562
562
|
gap: 0;
|
|
563
563
|
background:
|
|
564
564
|
linear-gradient(165deg, rgba(255, 251, 243, 0.95) 0%, rgba(247, 237, 223, 0.95) 100%);
|
|
@@ -631,42 +631,31 @@ body.mobile-actions-open .header-actions {
|
|
|
631
631
|
display: grid;
|
|
632
632
|
}
|
|
633
633
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
justify-content: space-between;
|
|
637
|
-
align-items: center;
|
|
638
|
-
gap: 10px;
|
|
639
|
-
margin: 10px 8px 4px;
|
|
640
|
-
min-height: 36px;
|
|
641
|
-
padding: 8px;
|
|
642
|
-
border: 1px solid var(--line);
|
|
643
|
-
border-radius: 12px;
|
|
644
|
-
background: rgba(255, 251, 244, 0.9);
|
|
634
|
+
#modeToggle {
|
|
635
|
+
position: relative;
|
|
645
636
|
}
|
|
646
637
|
|
|
647
|
-
.mode-
|
|
648
|
-
display:
|
|
649
|
-
|
|
638
|
+
.mode-menu {
|
|
639
|
+
display: none;
|
|
640
|
+
position: absolute;
|
|
641
|
+
top: calc(100% + 6px);
|
|
642
|
+
right: 8px;
|
|
643
|
+
z-index: 8;
|
|
644
|
+
width: min(200px, calc(100vw - 36px));
|
|
645
|
+
padding: 8px;
|
|
646
|
+
border: 1px solid var(--line);
|
|
647
|
+
border-radius: 10px;
|
|
648
|
+
background: #fffaf2;
|
|
649
|
+
box-shadow: var(--shadow-strong);
|
|
650
|
+
grid-template-columns: 1fr;
|
|
650
651
|
gap: 8px;
|
|
651
|
-
min-width: 0;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
.mode-switch button {
|
|
655
|
-
min-width: 102px;
|
|
656
|
-
color: var(--text);
|
|
657
|
-
background: #f7eee1;
|
|
658
|
-
border-color: #e0c6a5;
|
|
659
652
|
}
|
|
660
653
|
|
|
661
|
-
.mode-
|
|
662
|
-
|
|
663
|
-
background: var(--accent);
|
|
664
|
-
border-color: var(--accent-strong);
|
|
654
|
+
body.mode-menu-open .mode-menu {
|
|
655
|
+
display: grid;
|
|
665
656
|
}
|
|
666
657
|
|
|
667
|
-
|
|
668
|
-
body.agent-mode #modeAgentBtn,
|
|
669
|
-
body.terminal-mode #modeTerminalBtn {
|
|
658
|
+
.mode-menu button.is-active {
|
|
670
659
|
color: #ffffff;
|
|
671
660
|
background: var(--accent);
|
|
672
661
|
border-color: var(--accent-strong);
|
|
@@ -694,6 +683,39 @@ body.terminal-mode #modeTerminalBtn {
|
|
|
694
683
|
margin: 4px 0 0;
|
|
695
684
|
}
|
|
696
685
|
|
|
686
|
+
.term-keybar {
|
|
687
|
+
display: flex;
|
|
688
|
+
gap: 4px;
|
|
689
|
+
padding: 4px 6px;
|
|
690
|
+
background: #1a1a1a;
|
|
691
|
+
border-bottom: 1px solid rgba(255, 255, 255, 0.08);
|
|
692
|
+
flex-shrink: 0;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
.term-key-btn {
|
|
696
|
+
background: rgba(255, 255, 255, 0.07);
|
|
697
|
+
color: #bbb;
|
|
698
|
+
border: 1px solid rgba(255, 255, 255, 0.13);
|
|
699
|
+
border-radius: 4px;
|
|
700
|
+
padding: 2px 9px;
|
|
701
|
+
font-size: 12px;
|
|
702
|
+
font-family: monospace;
|
|
703
|
+
cursor: pointer;
|
|
704
|
+
user-select: none;
|
|
705
|
+
line-height: 1.6;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
.term-key-btn:hover {
|
|
709
|
+
background: rgba(255, 255, 255, 0.14);
|
|
710
|
+
color: #eee;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.term-key-btn.is-active {
|
|
714
|
+
background: var(--accent);
|
|
715
|
+
color: #fff;
|
|
716
|
+
border-color: var(--accent-strong);
|
|
717
|
+
}
|
|
718
|
+
|
|
697
719
|
#terminalScreen {
|
|
698
720
|
flex: 1;
|
|
699
721
|
min-height: 0;
|
|
@@ -1066,23 +1088,6 @@ body.command-mode .msg.origin-agent .bubble {
|
|
|
1066
1088
|
display: none;
|
|
1067
1089
|
}
|
|
1068
1090
|
|
|
1069
|
-
.mode-switch {
|
|
1070
|
-
margin: 8px 8px 2px;
|
|
1071
|
-
flex-wrap: wrap;
|
|
1072
|
-
padding: 8px;
|
|
1073
|
-
gap: 8px;
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
.mode-switch-left {
|
|
1077
|
-
flex: 1 1 auto;
|
|
1078
|
-
min-width: 0;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
.mode-switch button {
|
|
1082
|
-
flex: 0 0 auto;
|
|
1083
|
-
min-width: 84px;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
1091
|
#commandInput {
|
|
1087
1092
|
min-height: 68px;
|
|
1088
1093
|
max-height: 160px;
|
|
@@ -48,6 +48,13 @@
|
|
|
48
48
|
aria-controls="sessionList"
|
|
49
49
|
>会话</button>
|
|
50
50
|
<h1 id="activeTitle">未选择会话</h1>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
id="modeToggle"
|
|
54
|
+
class="secondary"
|
|
55
|
+
aria-expanded="false"
|
|
56
|
+
aria-controls="modeMenu"
|
|
57
|
+
>AGENT 模式</button>
|
|
51
58
|
<button
|
|
52
59
|
type="button"
|
|
53
60
|
id="mobileActionsToggle"
|
|
@@ -58,22 +65,29 @@
|
|
|
58
65
|
</div>
|
|
59
66
|
<div id="activeMeta">请选择左侧会话</div>
|
|
60
67
|
</div>
|
|
68
|
+
<div class="mode-menu" id="modeMenu">
|
|
69
|
+
<button type="button" id="modeAgentBtn" class="secondary is-active" aria-pressed="true">AGENT 模式</button>
|
|
70
|
+
<button type="button" id="modeCommandBtn" class="secondary" aria-pressed="false">命令模式</button>
|
|
71
|
+
<button type="button" id="modeTerminalBtn" class="secondary" aria-pressed="false">终端模式</button>
|
|
72
|
+
</div>
|
|
61
73
|
<div class="header-actions" id="headerActions">
|
|
62
74
|
<button type="button" id="refreshBtn" class="secondary">刷新</button>
|
|
63
75
|
<button type="button" id="removeBtn" class="danger-outline">删除容器</button>
|
|
64
76
|
<button type="button" id="removeAllBtn" class="danger">删除对话</button>
|
|
65
77
|
</div>
|
|
66
78
|
</header>
|
|
67
|
-
<section class="mode-switch" id="modeSwitch">
|
|
68
|
-
<div class="mode-switch-left">
|
|
69
|
-
<button type="button" id="modeAgentBtn" class="secondary is-active">AGENT 模式</button>
|
|
70
|
-
<button type="button" id="modeCommandBtn" class="secondary">命令模式</button>
|
|
71
|
-
<button type="button" id="modeTerminalBtn" class="secondary">交互终端</button>
|
|
72
|
-
</div>
|
|
73
|
-
|
|
74
|
-
</section>
|
|
75
79
|
<section id="messages"></section>
|
|
76
80
|
<section id="terminalPanel" hidden>
|
|
81
|
+
<div id="terminalKeybar" class="term-keybar" aria-label="终端快捷键">
|
|
82
|
+
<button type="button" class="term-key-btn" data-key="esc">esc</button>
|
|
83
|
+
<button type="button" class="term-key-btn" data-key="tab">tab</button>
|
|
84
|
+
<button type="button" class="term-key-btn term-ctrl-btn" data-key="ctrl">ctrl</button>
|
|
85
|
+
<button type="button" class="term-key-btn term-alt-btn" data-key="alt">alt</button>
|
|
86
|
+
<button type="button" class="term-key-btn" data-key="left"> ◀ </button>
|
|
87
|
+
<button type="button" class="term-key-btn" data-key="up"> ▲ </button>
|
|
88
|
+
<button type="button" class="term-key-btn" data-key="down"> ▼ </button>
|
|
89
|
+
<button type="button" class="term-key-btn" data-key="right"> ▶ </button>
|
|
90
|
+
</div>
|
|
77
91
|
<div id="terminalScreen" aria-label="终端输出区域"></div>
|
|
78
92
|
|
|
79
93
|
</section>
|
package/lib/web/frontend/app.js
CHANGED
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
loadingMessages: false,
|
|
47
47
|
mobileSidebarOpen: false,
|
|
48
48
|
mobileActionsOpen: false,
|
|
49
|
+
modeMenuOpen: false,
|
|
49
50
|
configModalOpen: false,
|
|
50
51
|
createModalOpen: false,
|
|
51
52
|
configLoading: false,
|
|
@@ -69,7 +70,9 @@
|
|
|
69
70
|
terminalReady: false,
|
|
70
71
|
fitTimer: null,
|
|
71
72
|
lastSentCols: 0,
|
|
72
|
-
lastSentRows: 0
|
|
73
|
+
lastSentRows: 0,
|
|
74
|
+
ctrlMode: false,
|
|
75
|
+
altMode: false
|
|
73
76
|
}
|
|
74
77
|
};
|
|
75
78
|
|
|
@@ -79,6 +82,8 @@
|
|
|
79
82
|
const mobileSessionToggle = document.getElementById('mobileSessionToggle');
|
|
80
83
|
const mobileActionsToggle = document.getElementById('mobileActionsToggle');
|
|
81
84
|
const headerActions = document.getElementById('headerActions');
|
|
85
|
+
const modeToggle = document.getElementById('modeToggle');
|
|
86
|
+
const modeMenu = document.getElementById('modeMenu');
|
|
82
87
|
const mobileSidebarClose = document.getElementById('mobileSidebarClose');
|
|
83
88
|
const sidebarBackdrop = document.getElementById('sidebarBackdrop');
|
|
84
89
|
const openConfigBtn = document.getElementById('openConfigBtn');
|
|
@@ -523,6 +528,11 @@
|
|
|
523
528
|
}) || null;
|
|
524
529
|
}
|
|
525
530
|
|
|
531
|
+
function isActiveSessionHistoryOnly() {
|
|
532
|
+
const session = getActiveSession();
|
|
533
|
+
return sessionStatusInfo(session && session.status).tone === 'history';
|
|
534
|
+
}
|
|
535
|
+
|
|
526
536
|
function isComposerMode() {
|
|
527
537
|
return state.mode === 'command' || state.mode === 'agent';
|
|
528
538
|
}
|
|
@@ -686,9 +696,18 @@
|
|
|
686
696
|
if (!data || !state.terminal.socket || state.terminal.socket.readyState !== window.WebSocket.OPEN) {
|
|
687
697
|
return;
|
|
688
698
|
}
|
|
699
|
+
let send = data;
|
|
700
|
+
if (state.terminal.ctrlMode && data.length === 1) {
|
|
701
|
+
const code = data.charCodeAt(0);
|
|
702
|
+
if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122)) {
|
|
703
|
+
send = String.fromCharCode(code & 0x1f);
|
|
704
|
+
}
|
|
705
|
+
} else if (state.terminal.altMode && data.length === 1) {
|
|
706
|
+
send = '\x1b' + data;
|
|
707
|
+
}
|
|
689
708
|
state.terminal.socket.send(JSON.stringify({
|
|
690
709
|
type: 'input',
|
|
691
|
-
data:
|
|
710
|
+
data: send
|
|
692
711
|
}));
|
|
693
712
|
});
|
|
694
713
|
state.terminal.term.onResize(function (size) {
|
|
@@ -768,6 +787,7 @@
|
|
|
768
787
|
state.terminal.sessionName = sessionName;
|
|
769
788
|
state.terminal.lastSentCols = 0;
|
|
770
789
|
state.terminal.lastSentRows = 0;
|
|
790
|
+
state.terminal.term.reset();
|
|
771
791
|
writeTerminalLine('[system] 正在连接终端...');
|
|
772
792
|
syncUi();
|
|
773
793
|
|
|
@@ -878,6 +898,20 @@
|
|
|
878
898
|
setMobileActionsMenu(false);
|
|
879
899
|
}
|
|
880
900
|
|
|
901
|
+
const MODE_LABELS = { agent: 'AGENT 模式', command: '命令模式', terminal: '终端模式' };
|
|
902
|
+
|
|
903
|
+
function setModeMenu(open) {
|
|
904
|
+
state.modeMenuOpen = Boolean(open);
|
|
905
|
+
document.body.classList.toggle('mode-menu-open', state.modeMenuOpen);
|
|
906
|
+
if (modeToggle) {
|
|
907
|
+
modeToggle.setAttribute('aria-expanded', state.modeMenuOpen ? 'true' : 'false');
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function closeModeMenu() {
|
|
912
|
+
setModeMenu(false);
|
|
913
|
+
}
|
|
914
|
+
|
|
881
915
|
function syncUi() {
|
|
882
916
|
if (!state.active) {
|
|
883
917
|
activeTitle.textContent = '未选择会话';
|
|
@@ -911,6 +945,9 @@
|
|
|
911
945
|
modeTerminalBtn.classList.toggle('is-active', terminalMode);
|
|
912
946
|
modeTerminalBtn.setAttribute('aria-pressed', terminalMode ? 'true' : 'false');
|
|
913
947
|
}
|
|
948
|
+
if (modeToggle) {
|
|
949
|
+
modeToggle.textContent = MODE_LABELS[state.mode] || '模式';
|
|
950
|
+
}
|
|
914
951
|
if (terminalPanel) {
|
|
915
952
|
terminalPanel.hidden = !terminalMode;
|
|
916
953
|
}
|
|
@@ -1164,7 +1201,11 @@
|
|
|
1164
1201
|
if (state.mode === 'terminal' && ensureTerminalReady()) {
|
|
1165
1202
|
scheduleTerminalFit(false);
|
|
1166
1203
|
if (!state.terminal.connected && !state.terminal.connecting) {
|
|
1167
|
-
|
|
1204
|
+
if (isActiveSessionHistoryOnly()) {
|
|
1205
|
+
state.terminal.term.reset();
|
|
1206
|
+
} else {
|
|
1207
|
+
connectTerminal();
|
|
1208
|
+
}
|
|
1168
1209
|
}
|
|
1169
1210
|
}
|
|
1170
1211
|
renderSessionActiveState();
|
|
@@ -1497,7 +1538,7 @@
|
|
|
1497
1538
|
throw requestError;
|
|
1498
1539
|
}
|
|
1499
1540
|
|
|
1500
|
-
if (state.mode === 'terminal' && ensureTerminalReady() && !state.terminal.connected && !state.terminal.connecting) {
|
|
1541
|
+
if (state.mode === 'terminal' && ensureTerminalReady() && !state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
|
|
1501
1542
|
scheduleTerminalFit(false);
|
|
1502
1543
|
connectTerminal();
|
|
1503
1544
|
}
|
|
@@ -1825,8 +1866,16 @@
|
|
|
1825
1866
|
composer.requestSubmit();
|
|
1826
1867
|
});
|
|
1827
1868
|
|
|
1869
|
+
if (modeToggle) {
|
|
1870
|
+
modeToggle.addEventListener('click', function () {
|
|
1871
|
+
closeMobileActionsMenu();
|
|
1872
|
+
setModeMenu(!state.modeMenuOpen);
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1828
1876
|
if (modeCommandBtn) {
|
|
1829
1877
|
modeCommandBtn.addEventListener('click', function () {
|
|
1878
|
+
closeModeMenu();
|
|
1830
1879
|
state.mode = 'command';
|
|
1831
1880
|
renderMessages(state.messages, { forceFullRender: true });
|
|
1832
1881
|
syncUi();
|
|
@@ -1836,6 +1885,7 @@
|
|
|
1836
1885
|
|
|
1837
1886
|
if (modeAgentBtn) {
|
|
1838
1887
|
modeAgentBtn.addEventListener('click', function () {
|
|
1888
|
+
closeModeMenu();
|
|
1839
1889
|
state.mode = 'agent';
|
|
1840
1890
|
renderMessages(state.messages, { forceFullRender: true });
|
|
1841
1891
|
syncUi();
|
|
@@ -1845,13 +1895,14 @@
|
|
|
1845
1895
|
|
|
1846
1896
|
if (modeTerminalBtn) {
|
|
1847
1897
|
modeTerminalBtn.addEventListener('click', function () {
|
|
1898
|
+
closeModeMenu();
|
|
1848
1899
|
state.mode = 'terminal';
|
|
1849
1900
|
renderMessages(state.messages, { forceFullRender: true });
|
|
1850
1901
|
syncUi();
|
|
1851
1902
|
if (ensureTerminalReady()) {
|
|
1852
1903
|
scheduleTerminalFit(false);
|
|
1853
1904
|
state.terminal.term.focus();
|
|
1854
|
-
if (!state.terminal.connected && !state.terminal.connecting) {
|
|
1905
|
+
if (!state.terminal.connected && !state.terminal.connecting && !isActiveSessionHistoryOnly()) {
|
|
1855
1906
|
connectTerminal();
|
|
1856
1907
|
}
|
|
1857
1908
|
}
|
|
@@ -1863,6 +1914,41 @@
|
|
|
1863
1914
|
loadSessions(state.active).catch(function (e) { alert(e.message); });
|
|
1864
1915
|
});
|
|
1865
1916
|
|
|
1917
|
+
const TERM_KEY_SEQUENCES = {
|
|
1918
|
+
esc: '\x1b',
|
|
1919
|
+
tab: '\x09',
|
|
1920
|
+
up: '\x1b[A',
|
|
1921
|
+
down: '\x1b[B',
|
|
1922
|
+
left: '\x1b[D',
|
|
1923
|
+
right: '\x1b[C'
|
|
1924
|
+
};
|
|
1925
|
+
const termKeybar = document.getElementById('terminalKeybar');
|
|
1926
|
+
if (termKeybar) {
|
|
1927
|
+
termKeybar.addEventListener('click', function (e) {
|
|
1928
|
+
const btn = e.target.closest('[data-key]');
|
|
1929
|
+
if (!btn) return;
|
|
1930
|
+
const key = btn.dataset.key;
|
|
1931
|
+
if (key === 'ctrl') {
|
|
1932
|
+
state.terminal.ctrlMode = !state.terminal.ctrlMode;
|
|
1933
|
+
btn.classList.toggle('is-active', state.terminal.ctrlMode);
|
|
1934
|
+
if (state.terminal.term) state.terminal.term.focus();
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
if (key === 'alt') {
|
|
1938
|
+
state.terminal.altMode = !state.terminal.altMode;
|
|
1939
|
+
btn.classList.toggle('is-active', state.terminal.altMode);
|
|
1940
|
+
if (state.terminal.term) state.terminal.term.focus();
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const seq = TERM_KEY_SEQUENCES[key];
|
|
1944
|
+
if (!seq) return;
|
|
1945
|
+
if (state.terminal.socket && state.terminal.socket.readyState === window.WebSocket.OPEN) {
|
|
1946
|
+
state.terminal.socket.send(JSON.stringify({ type: 'input', data: seq }));
|
|
1947
|
+
}
|
|
1948
|
+
if (state.terminal.term) state.terminal.term.focus();
|
|
1949
|
+
});
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1866
1952
|
if (mobileSessionToggle) {
|
|
1867
1953
|
mobileSessionToggle.addEventListener('click', function () {
|
|
1868
1954
|
setMobileSessionPanel(!state.mobileSidebarOpen);
|
|
@@ -1920,6 +2006,9 @@
|
|
|
1920
2006
|
if (event.key === 'Escape' && state.mobileActionsOpen) {
|
|
1921
2007
|
closeMobileActionsMenu();
|
|
1922
2008
|
}
|
|
2009
|
+
if (event.key === 'Escape' && state.modeMenuOpen) {
|
|
2010
|
+
closeModeMenu();
|
|
2011
|
+
}
|
|
1923
2012
|
});
|
|
1924
2013
|
|
|
1925
2014
|
function onLayoutMediaChange() {
|
|
@@ -1949,11 +2038,17 @@
|
|
|
1949
2038
|
}
|
|
1950
2039
|
|
|
1951
2040
|
document.addEventListener('click', function (event) {
|
|
1952
|
-
if (!state.mobileActionsOpen) return;
|
|
1953
2041
|
const target = event.target;
|
|
1954
|
-
if (
|
|
1955
|
-
|
|
1956
|
-
|
|
2042
|
+
if (state.mobileActionsOpen) {
|
|
2043
|
+
if (mobileActionsToggle && mobileActionsToggle.contains(target)) return;
|
|
2044
|
+
if (headerActions && headerActions.contains(target)) return;
|
|
2045
|
+
closeMobileActionsMenu();
|
|
2046
|
+
}
|
|
2047
|
+
if (state.modeMenuOpen) {
|
|
2048
|
+
if (modeToggle && modeToggle.contains(target)) return;
|
|
2049
|
+
if (modeMenu && modeMenu.contains(target)) return;
|
|
2050
|
+
closeModeMenu();
|
|
2051
|
+
}
|
|
1957
2052
|
});
|
|
1958
2053
|
|
|
1959
2054
|
removeBtn.addEventListener('click', async function () {
|
package/lib/web/server.js
CHANGED
|
@@ -1898,14 +1898,13 @@ async function startWebServer(options) {
|
|
|
1898
1898
|
const requestOrigin = req.headers.origin;
|
|
1899
1899
|
if (requestOrigin) {
|
|
1900
1900
|
const allowedOrigins = new Set();
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
} else {
|
|
1901
|
+
// 始终以请求的 Host 头构造允许来源,兼容 nginx 等反向代理场景
|
|
1902
|
+
const hostHeader = req.headers.host || '';
|
|
1903
|
+
if (hostHeader) {
|
|
1904
|
+
allowedOrigins.add(`http://${hostHeader}`);
|
|
1905
|
+
allowedOrigins.add(`https://${hostHeader}`);
|
|
1906
|
+
}
|
|
1907
|
+
if (ctx.serverHost !== '0.0.0.0') {
|
|
1909
1908
|
allowedOrigins.add(`http://${formatUrlHost(ctx.serverHost)}:${listenPort}`);
|
|
1910
1909
|
if (ctx.serverHost === '127.0.0.1') {
|
|
1911
1910
|
allowedOrigins.add(`http://localhost:${listenPort}`);
|