@co0ontty/wand 1.18.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/web-ui/content/scripts.js +297 -96
- package/dist/web-ui/content/styles.css +432 -23
- package/package.json +1 -1
|
@@ -337,8 +337,11 @@
|
|
|
337
337
|
var enabled = !!state.chatAutoFollow;
|
|
338
338
|
button.classList.toggle("active", enabled);
|
|
339
339
|
button.setAttribute("aria-pressed", enabled ? "true" : "false");
|
|
340
|
-
button.setAttribute("title", enabled ? "
|
|
341
|
-
button.
|
|
340
|
+
button.setAttribute("title", enabled ? "追踪底部:开启(点击暂停)" : "追踪底部:已暂停(点击开启)");
|
|
341
|
+
button.setAttribute("aria-label", enabled ? "追踪底部:开启" : "追踪底部:已暂停");
|
|
342
|
+
button.innerHTML = enabled
|
|
343
|
+
? '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 2.5l4.5 4.5 4.5-4.5"/><path d="M3.5 8.5l4.5 4.5 4.5-4.5"/></svg>'
|
|
344
|
+
: '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5.5 3v10"/><path d="M10.5 3v10"/></svg>';
|
|
342
345
|
}
|
|
343
346
|
|
|
344
347
|
function updateChatJumpToBottomButton() {
|
|
@@ -1208,7 +1211,7 @@
|
|
|
1208
1211
|
'</div>' +
|
|
1209
1212
|
'<div id="chat-output" class="chat-container hidden">' +
|
|
1210
1213
|
'<div class="chat-overlay-controls">' +
|
|
1211
|
-
'<button id="chat-follow-toggle" class="chat-follow-toggle topbar-btn' + (state.chatAutoFollow ? ' active' : '') + '" type="button" aria-pressed="' + (state.chatAutoFollow ? 'true' : 'false') + '"
|
|
1214
|
+
'<button id="chat-follow-toggle" class="chat-follow-toggle topbar-btn' + (state.chatAutoFollow ? ' active' : '') + '" type="button" aria-pressed="' + (state.chatAutoFollow ? 'true' : 'false') + '" aria-label="' + (state.chatAutoFollow ? '追踪底部:开启' : '追踪底部:已暂停') + '" title="' + (state.chatAutoFollow ? '追踪底部:开启(点击暂停)' : '追踪底部:已暂停(点击开启)') + '">' + (state.chatAutoFollow ? '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M3.5 2.5l4.5 4.5 4.5-4.5"/><path d="M3.5 8.5l4.5 4.5 4.5-4.5"/></svg>' : '<svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5.5 3v10"/><path d="M10.5 3v10"/></svg>') + '</button>' +
|
|
1212
1215
|
'</div>' +
|
|
1213
1216
|
'<button id="chat-jump-bottom" class="chat-jump-bottom' + (state.showChatJumpToBottom ? ' visible' : '') + '" type="button" title="回到底部并继续追底" aria-label="回到底部"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3.5v9M3.5 8l4.5 4.5L12.5 8"/></svg></button>' +
|
|
1214
1217
|
'</div>' +
|
|
@@ -1412,58 +1415,81 @@
|
|
|
1412
1415
|
'<div class="settings-about-row"><span class="settings-label">Node.js 要求</span><span class="settings-value" id="settings-node-req">-</span></div>' +
|
|
1413
1416
|
'<div class="settings-about-row"><span class="settings-label">仓库地址</span><span class="settings-value" id="settings-repo-url"><a href="#" target="_blank" rel="noopener">-</a></span></div>' +
|
|
1414
1417
|
'</div>' +
|
|
1415
|
-
'<div class="settings-update-section">' +
|
|
1418
|
+
'<div class="settings-update-section" id="web-update-section">' +
|
|
1419
|
+
'<div class="settings-section-head">' +
|
|
1420
|
+
'<span class="settings-section-icon">🌐</span>' +
|
|
1421
|
+
'<div class="settings-section-head-text">' +
|
|
1422
|
+
'<h4 class="settings-section-heading">Web 端</h4>' +
|
|
1423
|
+
'<p class="settings-section-sub">浏览器访问的服务版本</p>' +
|
|
1424
|
+
'</div>' +
|
|
1425
|
+
'</div>' +
|
|
1416
1426
|
'<div class="settings-about-row">' +
|
|
1417
1427
|
'<span class="settings-label">最新版本</span>' +
|
|
1418
1428
|
'<span class="settings-value" id="settings-latest-version">-</span>' +
|
|
1419
1429
|
'</div>' +
|
|
1420
1430
|
'<div class="settings-update-actions">' +
|
|
1421
|
-
'<button id="check-update-button" class="btn btn-
|
|
1431
|
+
'<button id="check-update-button" class="btn btn-secondary btn-sm">\u68c0\u67e5\u66f4\u65b0</button>' +
|
|
1422
1432
|
'<button id="do-update-button" class="btn btn-primary btn-sm hidden">\u66f4\u65b0\u5230\u6700\u65b0\u7248</button>' +
|
|
1423
1433
|
'<button id="do-restart-button" class="btn btn-success btn-sm hidden">\u91cd\u542f\u751f\u6548</button>' +
|
|
1424
1434
|
'</div>' +
|
|
1425
1435
|
'<p id="update-message" class="hint hidden"></p>' +
|
|
1426
|
-
'<div class="settings-
|
|
1427
|
-
'<
|
|
1428
|
-
|
|
1436
|
+
'<div class="settings-toggle-row">' +
|
|
1437
|
+
'<div class="settings-toggle-text">' +
|
|
1438
|
+
'<span class="settings-toggle-title">自动更新</span>' +
|
|
1439
|
+
'<span class="settings-toggle-desc">检测到新版本将自动下载安装并重启服务。</span>' +
|
|
1440
|
+
'</div>' +
|
|
1441
|
+
'<label class="settings-switch">' +
|
|
1429
1442
|
'<input type="checkbox" id="auto-update-web-toggle" class="switch-toggle">' +
|
|
1430
1443
|
'<span class="switch-slider"></span>' +
|
|
1431
1444
|
'</label>' +
|
|
1432
1445
|
'</div>' +
|
|
1433
|
-
'<p class="hint" style="margin-top:2px">开启后,检测到新版本将自动下载安装并重启服务。</p>' +
|
|
1434
1446
|
'</div>' +
|
|
1435
|
-
'<div class="settings-update-section" id="android-apk-section">' +
|
|
1447
|
+
'<div class="settings-update-section hidden" id="android-apk-section">' +
|
|
1448
|
+
'<div class="settings-section-head">' +
|
|
1449
|
+
'<span class="settings-section-icon">📱</span>' +
|
|
1450
|
+
'<div class="settings-section-head-text">' +
|
|
1451
|
+
'<h4 class="settings-section-heading">Android App</h4>' +
|
|
1452
|
+
'<p class="settings-section-sub">原生客户端版本与 APK 下载</p>' +
|
|
1453
|
+
'</div>' +
|
|
1454
|
+
'</div>' +
|
|
1436
1455
|
'<div id="android-apk-current-row" class="settings-about-row hidden">' +
|
|
1437
1456
|
'<span class="settings-label">当前版本</span>' +
|
|
1438
1457
|
'<span class="settings-value" id="settings-android-apk-current">-</span>' +
|
|
1439
1458
|
'</div>' +
|
|
1440
|
-
'<div id="android-apk-github-row" class="settings-about-row hidden">' +
|
|
1459
|
+
'<div id="android-apk-github-row" class="settings-about-row settings-about-row-action hidden">' +
|
|
1441
1460
|
'<span class="settings-label">线上版本</span>' +
|
|
1442
|
-
'<span class="settings-value" id="settings-android-apk-github"
|
|
1443
|
-
'<button id="download-github-apk-btn" class="btn btn-
|
|
1461
|
+
'<span class="settings-value settings-value-flex" id="settings-android-apk-github">-</span>' +
|
|
1462
|
+
'<button id="download-github-apk-btn" class="btn btn-secondary btn-sm hidden" type="button">下载</button>' +
|
|
1444
1463
|
'</div>' +
|
|
1445
|
-
'<div id="android-apk-local-row" class="settings-about-row hidden">' +
|
|
1464
|
+
'<div id="android-apk-local-row" class="settings-about-row settings-about-row-action hidden">' +
|
|
1446
1465
|
'<span class="settings-label">本地版本</span>' +
|
|
1447
|
-
'<span class="settings-value" id="settings-android-apk-local"
|
|
1448
|
-
'<button id="download-local-apk-btn" class="btn btn-
|
|
1466
|
+
'<span class="settings-value settings-value-flex" id="settings-android-apk-local">-</span>' +
|
|
1467
|
+
'<button id="download-local-apk-btn" class="btn btn-secondary btn-sm hidden" type="button">下载</button>' +
|
|
1449
1468
|
'</div>' +
|
|
1450
|
-
'<div id="android-auto-update-row" class="
|
|
1451
|
-
'<
|
|
1452
|
-
|
|
1469
|
+
'<div id="android-auto-update-row" class="settings-toggle-row hidden">' +
|
|
1470
|
+
'<div class="settings-toggle-text">' +
|
|
1471
|
+
'<span class="settings-toggle-title">自动更新</span>' +
|
|
1472
|
+
'<span class="settings-toggle-desc" id="android-auto-update-hint">检测到新版 APK 将自动下载安装。</span>' +
|
|
1473
|
+
'</div>' +
|
|
1474
|
+
'<label class="settings-switch">' +
|
|
1453
1475
|
'<input type="checkbox" id="auto-update-apk-toggle" class="switch-toggle">' +
|
|
1454
1476
|
'<span class="switch-slider"></span>' +
|
|
1455
1477
|
'</label>' +
|
|
1456
1478
|
'</div>' +
|
|
1457
|
-
'<p id="android-auto-update-hint" class="hint hidden" style="margin-top:2px">开启后,检测到新版 APK 将自动下载安装。</p>' +
|
|
1458
1479
|
'<p id="android-apk-message" class="hint hidden"></p>' +
|
|
1459
1480
|
'</div>' +
|
|
1460
1481
|
'<div class="settings-update-section" id="android-connect-section">' +
|
|
1461
|
-
'<div class="settings-section-
|
|
1482
|
+
'<div class="settings-section-head">' +
|
|
1483
|
+
'<span class="settings-section-icon">🔗</span>' +
|
|
1484
|
+
'<div class="settings-section-head-text">' +
|
|
1485
|
+
'<h4 class="settings-section-heading">App 连接码</h4>' +
|
|
1486
|
+
'<p class="settings-section-sub">粘贴到 Android App 即可自动连接,无需密码;改密码后失效。</p>' +
|
|
1487
|
+
'</div>' +
|
|
1488
|
+
'</div>' +
|
|
1462
1489
|
'<div class="settings-connect-url-box">' +
|
|
1463
|
-
'<code id="android-connect-code" class="settings-connect-url-text"
|
|
1464
|
-
'<button id="copy-connect-code-button" class="btn btn-
|
|
1490
|
+
'<code id="android-connect-code" class="settings-connect-url-text">-</code>' +
|
|
1491
|
+
'<button id="copy-connect-code-button" class="btn btn-secondary btn-sm" type="button" title="复制连接码">复制</button>' +
|
|
1465
1492
|
'</div>' +
|
|
1466
|
-
'<p class="hint">复制此连接码粘贴到 Android App 即可自动连接,无需输入密码。修改密码后连接码自动失效。</p>' +
|
|
1467
1493
|
'</div>' +
|
|
1468
1494
|
'</div>' +
|
|
1469
1495
|
|
|
@@ -1473,54 +1499,88 @@
|
|
|
1473
1499
|
'<h3 class="settings-panel-title">通知</h3>' +
|
|
1474
1500
|
'<p class="settings-panel-desc">设置提示音、系统通知和浏览器通知的行为。</p>' +
|
|
1475
1501
|
'</div>' +
|
|
1476
|
-
'<div class="settings-section
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
'
|
|
1484
|
-
'<div
|
|
1485
|
-
'<
|
|
1486
|
-
|
|
1502
|
+
'<div class="settings-notification-section">' +
|
|
1503
|
+
'<div class="settings-section-head">' +
|
|
1504
|
+
'<span class="settings-section-icon">🔔</span>' +
|
|
1505
|
+
'<div class="settings-section-head-text">' +
|
|
1506
|
+
'<h4 class="settings-section-heading">通知偏好</h4>' +
|
|
1507
|
+
'<p class="settings-section-sub">提示音与应用内通知气泡</p>' +
|
|
1508
|
+
'</div>' +
|
|
1509
|
+
'</div>' +
|
|
1510
|
+
'<div class="settings-toggle-row">' +
|
|
1511
|
+
'<div class="settings-toggle-text">' +
|
|
1512
|
+
'<label class="settings-toggle-title" for="cfg-notif-sound">播放提示音</label>' +
|
|
1513
|
+
'<span class="settings-toggle-desc">重要通知(版本更新、权限等待等)时播放柔和提示音。</span>' +
|
|
1514
|
+
'</div>' +
|
|
1515
|
+
'<label class="settings-switch">' +
|
|
1516
|
+
'<input id="cfg-notif-sound" type="checkbox" class="switch-toggle" />' +
|
|
1517
|
+
'<span class="switch-slider"></span>' +
|
|
1518
|
+
'</label>' +
|
|
1519
|
+
'</div>' +
|
|
1520
|
+
'<div class="settings-range-row" id="notif-volume-field">' +
|
|
1521
|
+
'<label class="settings-range-label" for="cfg-notif-volume">音量</label>' +
|
|
1522
|
+
'<input id="cfg-notif-volume" type="range" min="0" max="100" step="5" class="settings-range" />' +
|
|
1523
|
+
'<span id="cfg-notif-volume-val" class="settings-range-value">80%</span>' +
|
|
1524
|
+
'</div>' +
|
|
1525
|
+
'<div class="settings-toggle-row">' +
|
|
1526
|
+
'<div class="settings-toggle-text">' +
|
|
1527
|
+
'<label class="settings-toggle-title" for="cfg-notif-bubble">应用内通知气泡</label>' +
|
|
1528
|
+
'<span class="settings-toggle-desc">在页面顶部弹出浮动通知气泡。</span>' +
|
|
1529
|
+
'</div>' +
|
|
1530
|
+
'<label class="settings-switch">' +
|
|
1531
|
+
'<input id="cfg-notif-bubble" type="checkbox" class="switch-toggle" />' +
|
|
1532
|
+
'<span class="switch-slider"></span>' +
|
|
1533
|
+
'</label>' +
|
|
1487
1534
|
'</div>' +
|
|
1488
1535
|
'</div>' +
|
|
1489
|
-
'<div class="
|
|
1490
|
-
'<
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
'<div class="settings-section-title">\u7cfb\u7edf\u901a\u77e5\u94c3\u58f0</div>' +
|
|
1496
|
-
'<div class="settings-about-row">' +
|
|
1497
|
-
'<span class="settings-label">\u94c3\u58f0</span>' +
|
|
1498
|
-
'<div style="display:flex;align-items:center;gap:6px">' +
|
|
1499
|
-
'<select id="native-sound-select" class="field-select" style="min-width:100px"></select>' +
|
|
1500
|
-
'<button id="native-sound-preview" class="btn btn-ghost btn-sm">\u25b6 \u8bd5\u542c</button>' +
|
|
1536
|
+
'<div id="native-sound-section" class="settings-notification-section hidden">' +
|
|
1537
|
+
'<div class="settings-section-head">' +
|
|
1538
|
+
'<span class="settings-section-icon">🎵</span>' +
|
|
1539
|
+
'<div class="settings-section-head-text">' +
|
|
1540
|
+
'<h4 class="settings-section-heading">系统通知铃声</h4>' +
|
|
1541
|
+
'<p class="settings-section-sub">选择 Android 系统通知使用的铃声</p>' +
|
|
1501
1542
|
'</div>' +
|
|
1502
1543
|
'</div>' +
|
|
1503
|
-
'<
|
|
1544
|
+
'<div class="settings-row-with-action">' +
|
|
1545
|
+
'<select id="native-sound-select" class="field-input field-select"></select>' +
|
|
1546
|
+
'<button id="native-sound-preview" class="btn btn-secondary btn-sm" type="button">▶ 试听</button>' +
|
|
1547
|
+
'</div>' +
|
|
1504
1548
|
'</div>' +
|
|
1505
|
-
'<div id="native-haptic-section" class="settings-notification-section hidden"
|
|
1506
|
-
'<div class="settings-section-
|
|
1507
|
-
|
|
1508
|
-
'<
|
|
1509
|
-
|
|
1549
|
+
'<div id="native-haptic-section" class="settings-notification-section hidden">' +
|
|
1550
|
+
'<div class="settings-section-head">' +
|
|
1551
|
+
'<span class="settings-section-icon">📳</span>' +
|
|
1552
|
+
'<div class="settings-section-head-text">' +
|
|
1553
|
+
'<h4 class="settings-section-heading">触感反馈</h4>' +
|
|
1554
|
+
'<p class="settings-section-sub">按钮操作和任务完成时提供振动反馈</p>' +
|
|
1555
|
+
'</div>' +
|
|
1556
|
+
'</div>' +
|
|
1557
|
+
'<div class="settings-toggle-row">' +
|
|
1558
|
+
'<div class="settings-toggle-text">' +
|
|
1559
|
+
'<label class="settings-toggle-title" for="cfg-haptic-enabled">启用触感反馈</label>' +
|
|
1560
|
+
'</div>' +
|
|
1561
|
+
'<label class="settings-switch">' +
|
|
1562
|
+
'<input id="cfg-haptic-enabled" type="checkbox" class="switch-toggle" />' +
|
|
1563
|
+
'<span class="switch-slider"></span>' +
|
|
1564
|
+
'</label>' +
|
|
1510
1565
|
'</div>' +
|
|
1511
|
-
'<p class="hint" style="margin-top:0">\u6309\u94ae\u64cd\u4f5c\u548c\u4efb\u52a1\u5b8c\u6210\u65f6\u63d0\u4f9b\u632f\u52a8\u53cd\u9988</p>' +
|
|
1512
1566
|
'</div>' +
|
|
1513
|
-
'<div class="settings-notification-section"
|
|
1514
|
-
'<div class="settings-section-
|
|
1567
|
+
'<div class="settings-notification-section">' +
|
|
1568
|
+
'<div class="settings-section-head">' +
|
|
1569
|
+
'<span class="settings-section-icon">🌐</span>' +
|
|
1570
|
+
'<div class="settings-section-head-text">' +
|
|
1571
|
+
'<h4 class="settings-section-heading">浏览器通知</h4>' +
|
|
1572
|
+
'<p class="settings-section-sub">来自系统通知中心的弹窗</p>' +
|
|
1573
|
+
'</div>' +
|
|
1574
|
+
'</div>' +
|
|
1515
1575
|
'<div class="settings-about-row">' +
|
|
1516
|
-
'<span class="settings-label"
|
|
1576
|
+
'<span class="settings-label">授权状态</span>' +
|
|
1517
1577
|
'<span class="settings-value" id="notification-permission-status">-</span>' +
|
|
1518
1578
|
'</div>' +
|
|
1519
1579
|
'<div class="settings-update-actions">' +
|
|
1520
|
-
'<button id="notification-request-btn" class="btn btn-
|
|
1521
|
-
'<button id="notification-reset-btn" class="btn btn-
|
|
1522
|
-
'<button id="notification-test-btn" class="btn btn-
|
|
1523
|
-
'<button id="notification-test-delay-btn" class="btn btn-
|
|
1580
|
+
'<button id="notification-request-btn" class="btn btn-secondary btn-sm hidden" type="button">授权通知</button>' +
|
|
1581
|
+
'<button id="notification-reset-btn" class="btn btn-secondary btn-sm hidden" type="button">重新授权</button>' +
|
|
1582
|
+
'<button id="notification-test-btn" class="btn btn-secondary btn-sm" type="button">发送测试通知</button>' +
|
|
1583
|
+
'<button id="notification-test-delay-btn" class="btn btn-secondary btn-sm" type="button">10 秒后发送</button>' +
|
|
1524
1584
|
'</div>' +
|
|
1525
1585
|
'<p id="notification-test-message" class="hint hidden"></p>' +
|
|
1526
1586
|
'</div>' +
|
|
@@ -1578,13 +1638,13 @@
|
|
|
1578
1638
|
'<p class="field-hint" style="margin-top:-4px;">设置回复语言后,Claude 将尽量使用指定语言回复。</p>' +
|
|
1579
1639
|
'<div class="field">' +
|
|
1580
1640
|
'<label class="field-label" for="cfg-default-model">默认模型</label>' +
|
|
1581
|
-
'<div
|
|
1582
|
-
'<select id="cfg-default-model" class="field-input
|
|
1641
|
+
'<div class="settings-row-with-action">' +
|
|
1642
|
+
'<select id="cfg-default-model" class="field-input field-select">' +
|
|
1583
1643
|
'<option value="">跟随 Claude Code 默认</option>' +
|
|
1584
1644
|
'</select>' +
|
|
1585
|
-
'<button type="button" id="cfg-default-model-refresh" class="btn btn-
|
|
1645
|
+
'<button type="button" id="cfg-default-model-refresh" class="btn btn-secondary btn-sm" title="刷新模型列表">刷新</button>' +
|
|
1586
1646
|
'</div>' +
|
|
1587
|
-
'<p class="field-hint" id="cfg-default-model-version"
|
|
1647
|
+
'<p class="field-hint" id="cfg-default-model-version">新建会话时默认使用该模型;运行中的会话可在输入框切换。</p>' +
|
|
1588
1648
|
'</div>' +
|
|
1589
1649
|
'<div class="field">' +
|
|
1590
1650
|
'<label class="field-label" for="cfg-cwd">默认工作目录</label>' +
|
|
@@ -1595,24 +1655,29 @@
|
|
|
1595
1655
|
'<input id="cfg-shell" type="text" class="field-input" placeholder="/bin/bash" />' +
|
|
1596
1656
|
'</div>' +
|
|
1597
1657
|
(typeof WandNative !== "undefined" && typeof WandNative.getAppIcon === "function" ?
|
|
1598
|
-
'<div
|
|
1599
|
-
'<div class="settings-section-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
'<
|
|
1604
|
-
PIXEL_AVATAR.user +
|
|
1605
|
-
'</div>' +
|
|
1606
|
-
'<span style="font-size:0.72rem;color:var(--text-secondary)">赛博虎妞</span>' +
|
|
1658
|
+
'<div class="settings-app-icon-block">' +
|
|
1659
|
+
'<div class="settings-section-head">' +
|
|
1660
|
+
'<span class="settings-section-icon">🎨</span>' +
|
|
1661
|
+
'<div class="settings-section-head-text">' +
|
|
1662
|
+
'<h4 class="settings-section-heading">应用图标</h4>' +
|
|
1663
|
+
'<p class="settings-section-sub">选择 App 启动器图标,返回桌面后生效</p>' +
|
|
1607
1664
|
'</div>' +
|
|
1608
|
-
|
|
1609
|
-
|
|
1665
|
+
'</div>' +
|
|
1666
|
+
'<div id="app-icon-picker" class="settings-app-icon-picker">' +
|
|
1667
|
+
'<button type="button" class="settings-app-icon-option" data-icon="shorthair">' +
|
|
1668
|
+
'<span class="settings-app-icon-preview">' +
|
|
1669
|
+
PIXEL_AVATAR.user +
|
|
1670
|
+
'</span>' +
|
|
1671
|
+
'<span class="settings-app-icon-label">赛博虎妞</span>' +
|
|
1672
|
+
'</button>' +
|
|
1673
|
+
'<button type="button" class="settings-app-icon-option" data-icon="garfield">' +
|
|
1674
|
+
'<span class="settings-app-icon-preview">' +
|
|
1610
1675
|
PIXEL_AVATAR.assistant +
|
|
1611
|
-
'</
|
|
1612
|
-
'<span
|
|
1613
|
-
'</
|
|
1676
|
+
'</span>' +
|
|
1677
|
+
'<span class="settings-app-icon-label">勤劳初二</span>' +
|
|
1678
|
+
'</button>' +
|
|
1614
1679
|
'</div>' +
|
|
1615
|
-
'<p id="app-icon-message" class="hint hidden"
|
|
1680
|
+
'<p id="app-icon-message" class="hint hidden"></p>' +
|
|
1616
1681
|
'</div>'
|
|
1617
1682
|
: '') +
|
|
1618
1683
|
'<div class="settings-actions settings-actions-sticky">' +
|
|
@@ -1628,7 +1693,13 @@
|
|
|
1628
1693
|
'<p class="settings-panel-desc">管理登录密码与 SSL 证书,敏感变更请确认后再保存。</p>' +
|
|
1629
1694
|
'</div>' +
|
|
1630
1695
|
'<div class="settings-card">' +
|
|
1631
|
-
'<
|
|
1696
|
+
'<div class="settings-card-head">' +
|
|
1697
|
+
'<span class="settings-card-icon">\ud83d\udd12</span>' +
|
|
1698
|
+
'<div class="settings-card-head-text">' +
|
|
1699
|
+
'<h3 class="settings-card-title">修改密码</h3>' +
|
|
1700
|
+
'<p class="settings-card-desc">至少 6 个字符;保存后下次登录生效。</p>' +
|
|
1701
|
+
'</div>' +
|
|
1702
|
+
'</div>' +
|
|
1632
1703
|
'<div class="field">' +
|
|
1633
1704
|
'<label class="field-label" for="new-password">新密码</label>' +
|
|
1634
1705
|
'<input id="new-password" type="password" class="field-input" placeholder="输入新密码(至少 6 个字符)" autocomplete="new-password" />' +
|
|
@@ -1637,22 +1708,45 @@
|
|
|
1637
1708
|
'<label class="field-label" for="confirm-password">确认密码</label>' +
|
|
1638
1709
|
'<input id="confirm-password" type="password" class="field-input" placeholder="再次输入新密码" autocomplete="new-password" />' +
|
|
1639
1710
|
'</div>' +
|
|
1640
|
-
'<
|
|
1711
|
+
'<div class="settings-card-actions">' +
|
|
1712
|
+
'<button id="save-password-button" class="btn btn-primary">保存密码</button>' +
|
|
1713
|
+
'</div>' +
|
|
1641
1714
|
'<p id="settings-error" class="error-message hidden"></p>' +
|
|
1642
|
-
'<p id="settings-success" class="hint hidden"
|
|
1715
|
+
'<p id="settings-success" class="hint settings-success-message hidden"></p>' +
|
|
1643
1716
|
'</div>' +
|
|
1644
1717
|
'<div class="settings-card">' +
|
|
1645
|
-
'<
|
|
1646
|
-
|
|
1718
|
+
'<div class="settings-card-head">' +
|
|
1719
|
+
'<span class="settings-card-icon">\ud83d\udd10</span>' +
|
|
1720
|
+
'<div class="settings-card-head-text">' +
|
|
1721
|
+
'<h3 class="settings-card-title">SSL 证书</h3>' +
|
|
1722
|
+
'<p class="settings-card-desc" id="cert-status">加载中...</p>' +
|
|
1723
|
+
'</div>' +
|
|
1724
|
+
'</div>' +
|
|
1647
1725
|
'<div class="field">' +
|
|
1648
1726
|
'<label class="field-label" for="cert-key-file">私钥文件 (.key)</label>' +
|
|
1649
|
-
'<
|
|
1727
|
+
'<div class="file-picker">' +
|
|
1728
|
+
'<input id="cert-key-file" type="file" class="file-picker-input" accept=".key,.pem" />' +
|
|
1729
|
+
'<label for="cert-key-file" class="file-picker-trigger">' +
|
|
1730
|
+
'<svg class="file-picker-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg>' +
|
|
1731
|
+
'<span class="file-picker-label">选择私钥</span>' +
|
|
1732
|
+
'</label>' +
|
|
1733
|
+
'<span class="file-picker-name" data-default="未选择文件">未选择文件</span>' +
|
|
1734
|
+
'</div>' +
|
|
1650
1735
|
'</div>' +
|
|
1651
1736
|
'<div class="field">' +
|
|
1652
1737
|
'<label class="field-label" for="cert-cert-file">证书文件 (.crt/.pem)</label>' +
|
|
1653
|
-
'<
|
|
1738
|
+
'<div class="file-picker">' +
|
|
1739
|
+
'<input id="cert-cert-file" type="file" class="file-picker-input" accept=".crt,.pem,.cert" />' +
|
|
1740
|
+
'<label for="cert-cert-file" class="file-picker-trigger">' +
|
|
1741
|
+
'<svg class="file-picker-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline></svg>' +
|
|
1742
|
+
'<span class="file-picker-label">选择证书</span>' +
|
|
1743
|
+
'</label>' +
|
|
1744
|
+
'<span class="file-picker-name" data-default="未选择文件">未选择文件</span>' +
|
|
1745
|
+
'</div>' +
|
|
1746
|
+
'</div>' +
|
|
1747
|
+
'<div class="settings-card-actions">' +
|
|
1748
|
+
'<button id="upload-cert-button" class="btn btn-primary">上传证书</button>' +
|
|
1654
1749
|
'</div>' +
|
|
1655
|
-
'<button id="upload-cert-button" class="btn btn-primary btn-block">上传证书</button>' +
|
|
1656
1750
|
'<p id="cert-message" class="hint hidden"></p>' +
|
|
1657
1751
|
'</div>' +
|
|
1658
1752
|
'</div>' +
|
|
@@ -3363,7 +3457,7 @@
|
|
|
3363
3457
|
// App icon picker (APK only)
|
|
3364
3458
|
var appIconPicker = document.getElementById("app-icon-picker");
|
|
3365
3459
|
if (appIconPicker) {
|
|
3366
|
-
var appIconOpts = appIconPicker.querySelectorAll(".app-icon-option");
|
|
3460
|
+
var appIconOpts = appIconPicker.querySelectorAll(".settings-app-icon-option");
|
|
3367
3461
|
for (var ai = 0; ai < appIconOpts.length; ai++) {
|
|
3368
3462
|
appIconOpts[ai].addEventListener("click", function() {
|
|
3369
3463
|
var iconName = this.getAttribute("data-icon");
|
|
@@ -3384,6 +3478,24 @@
|
|
|
3384
3478
|
}
|
|
3385
3479
|
var uploadCertBtn = document.getElementById("upload-cert-button");
|
|
3386
3480
|
if (uploadCertBtn) uploadCertBtn.addEventListener("click", uploadCertificates);
|
|
3481
|
+
var filePickerInputs = document.querySelectorAll(".file-picker-input");
|
|
3482
|
+
for (var fpi = 0; fpi < filePickerInputs.length; fpi++) {
|
|
3483
|
+
(function(input) {
|
|
3484
|
+
input.addEventListener("change", function() {
|
|
3485
|
+
var picker = input.closest(".file-picker");
|
|
3486
|
+
if (!picker) return;
|
|
3487
|
+
var nameEl = picker.querySelector(".file-picker-name");
|
|
3488
|
+
if (!nameEl) return;
|
|
3489
|
+
if (input.files && input.files[0]) {
|
|
3490
|
+
nameEl.textContent = input.files[0].name;
|
|
3491
|
+
picker.classList.add("file-picker-has-file");
|
|
3492
|
+
} else {
|
|
3493
|
+
nameEl.textContent = nameEl.getAttribute("data-default") || "未选择文件";
|
|
3494
|
+
picker.classList.remove("file-picker-has-file");
|
|
3495
|
+
}
|
|
3496
|
+
});
|
|
3497
|
+
})(filePickerInputs[fpi]);
|
|
3498
|
+
}
|
|
3387
3499
|
var checkUpdateBtn = document.getElementById("check-update-button");
|
|
3388
3500
|
if (checkUpdateBtn) checkUpdateBtn.addEventListener("click", checkForUpdate);
|
|
3389
3501
|
var doUpdateBtn = document.getElementById("do-update-button");
|
|
@@ -4564,6 +4676,9 @@
|
|
|
4564
4676
|
// Remeasure against real container: the refresh button used to only
|
|
4565
4677
|
// reset+write, so a stale cols/rows (set at mount time with hidden
|
|
4566
4678
|
// container) would survive the refresh and keep wrapping output wrong.
|
|
4679
|
+
// Suppress the auto-replay branch in ensureTerminalFit — we just
|
|
4680
|
+
// replayed, no point doing it again on the next rAF tick.
|
|
4681
|
+
state.suppressFitReplay = true;
|
|
4567
4682
|
ensureTerminalFit("refresh");
|
|
4568
4683
|
return true;
|
|
4569
4684
|
}
|
|
@@ -4688,7 +4803,16 @@
|
|
|
4688
4803
|
}
|
|
4689
4804
|
});
|
|
4690
4805
|
|
|
4691
|
-
|
|
4806
|
+
// Wait for the monospace webfont (if any) before init so the very first
|
|
4807
|
+
// _measureCharSize() inside wterm uses the final glyph metrics. Otherwise
|
|
4808
|
+
// the fallback font's narrower glyphs make wterm calculate too many cols,
|
|
4809
|
+
// and subsequent chunks render with broken wrapping until the user
|
|
4810
|
+
// triggers a resize. Cap the wait so a missing font never blocks startup.
|
|
4811
|
+
var fontsReady = (document.fonts && typeof document.fonts.ready === "object")
|
|
4812
|
+
? Promise.race([document.fonts.ready, new Promise(function(r) { setTimeout(r, 800); })])
|
|
4813
|
+
: Promise.resolve();
|
|
4814
|
+
|
|
4815
|
+
fontsReady.then(function() { return term.init(); }).then(function() {
|
|
4692
4816
|
state.terminal = term;
|
|
4693
4817
|
state.terminalInitializing = false;
|
|
4694
4818
|
applyTerminalScale();
|
|
@@ -6220,6 +6344,11 @@
|
|
|
6220
6344
|
var apkMessageEl = document.getElementById("android-apk-message");
|
|
6221
6345
|
var androidApk = data.androidApk || {};
|
|
6222
6346
|
var isInApk = !!_apkVersion;
|
|
6347
|
+
var hasApkInfo = isInApk || !!androidApk.github || !!androidApk.local;
|
|
6348
|
+
if (apkSection) {
|
|
6349
|
+
if (hasApkInfo) apkSection.classList.remove("hidden");
|
|
6350
|
+
else apkSection.classList.add("hidden");
|
|
6351
|
+
}
|
|
6223
6352
|
|
|
6224
6353
|
if (isInApk) {
|
|
6225
6354
|
// ── APK 内模式:显示当前版本 + 线上版本 + 本地版本 ──
|
|
@@ -6663,12 +6792,11 @@
|
|
|
6663
6792
|
// ── Notification Settings Helpers ──
|
|
6664
6793
|
|
|
6665
6794
|
function _updateAppIconSelection(activeIcon) {
|
|
6666
|
-
var opts = document.querySelectorAll(".app-icon-option");
|
|
6795
|
+
var opts = document.querySelectorAll(".settings-app-icon-option");
|
|
6667
6796
|
for (var i = 0; i < opts.length; i++) {
|
|
6668
|
-
var
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
}
|
|
6797
|
+
var isActive = opts[i].getAttribute("data-icon") === activeIcon;
|
|
6798
|
+
opts[i].classList.toggle("selected", isActive);
|
|
6799
|
+
opts[i].setAttribute("aria-pressed", isActive ? "true" : "false");
|
|
6672
6800
|
}
|
|
6673
6801
|
}
|
|
6674
6802
|
|
|
@@ -10019,15 +10147,30 @@
|
|
|
10019
10147
|
syncInputBoxScroll(inputBox);
|
|
10020
10148
|
}
|
|
10021
10149
|
|
|
10150
|
+
// Keyboard just opened — terminal viewport now shares space with
|
|
10151
|
+
// the keyboard; visible rows shrink even if cols stayed the same.
|
|
10152
|
+
// Without an immediate refit, any chunk arriving while the keyboard
|
|
10153
|
+
// animates in renders against the old grid and tears the screen.
|
|
10154
|
+
if (!keyboardOpen && isKeyboardOpen) {
|
|
10155
|
+
ensureTerminalFit("keyboard-open");
|
|
10156
|
+
}
|
|
10157
|
+
|
|
10022
10158
|
// Keyboard just closed — force terminal refit and scroll to bottom
|
|
10023
10159
|
// after a delay so the keyboard dismiss animation and layout settle.
|
|
10024
10160
|
if (keyboardOpen && !isKeyboardOpen) {
|
|
10025
10161
|
setTimeout(function() {
|
|
10026
|
-
ensureTerminalFit();
|
|
10162
|
+
ensureTerminalFit("keyboard-close");
|
|
10027
10163
|
maybeScrollTerminalToBottom("force");
|
|
10028
10164
|
}, 200);
|
|
10029
10165
|
}
|
|
10030
10166
|
|
|
10167
|
+
// visualViewport height changed without a keyboard transition —
|
|
10168
|
+
// covers iOS address-bar collapse/expand and split-screen drag.
|
|
10169
|
+
// Cheap to call: ensureTerminalFit early-exits if cols/rows stable.
|
|
10170
|
+
if (heightChanged && keyboardOpen === isKeyboardOpen) {
|
|
10171
|
+
ensureTerminalFit("viewport");
|
|
10172
|
+
}
|
|
10173
|
+
|
|
10031
10174
|
keyboardOpen = isKeyboardOpen;
|
|
10032
10175
|
lastHeight = vv.height;
|
|
10033
10176
|
}
|
|
@@ -10284,6 +10427,8 @@
|
|
|
10284
10427
|
if (!state.terminal) return false;
|
|
10285
10428
|
var el = document.getElementById("output");
|
|
10286
10429
|
if (!el || el.offsetWidth === 0 || el.offsetHeight === 0) return false;
|
|
10430
|
+
var prevCols = state.terminal.cols;
|
|
10431
|
+
var prevRows = state.terminal.rows;
|
|
10287
10432
|
requestAnimationFrame(function() {
|
|
10288
10433
|
requestAnimationFrame(function() {
|
|
10289
10434
|
if (!state.terminal) return;
|
|
@@ -10291,6 +10436,21 @@
|
|
|
10291
10436
|
state.terminal.remeasure();
|
|
10292
10437
|
}
|
|
10293
10438
|
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
10439
|
+
// Cache the container width that produced this cols/rows so the
|
|
10440
|
+
// hot-path chunk writer can detect drift cheaply (avoids running
|
|
10441
|
+
// a full remeasure on every WebSocket message).
|
|
10442
|
+
state.lastFitContainerWidth = el.offsetWidth;
|
|
10443
|
+
state.lastFitContainerHeight = el.offsetHeight;
|
|
10444
|
+
// If cols actually changed, the previously written buffer was
|
|
10445
|
+
// wrapped to the old width. Replay the full buffer so historical
|
|
10446
|
+
// lines and any in-flight CSI cursor sequences re-render against
|
|
10447
|
+
// the new grid — this is what fixes the "torn" screens users see
|
|
10448
|
+
// after rotating, opening the keyboard, or resizing the panel.
|
|
10449
|
+
var skipReplay = state.suppressFitReplay === true;
|
|
10450
|
+
state.suppressFitReplay = false;
|
|
10451
|
+
if (!skipReplay && (state.terminal.cols !== prevCols || state.terminal.rows !== prevRows)) {
|
|
10452
|
+
if (state.terminalOutput) softResyncTerminal();
|
|
10453
|
+
}
|
|
10294
10454
|
if (state.terminalAutoFollow || isTerminalNearBottom()) {
|
|
10295
10455
|
maybeScrollTerminalToBottom("resize");
|
|
10296
10456
|
}
|
|
@@ -10299,6 +10459,43 @@
|
|
|
10299
10459
|
return true;
|
|
10300
10460
|
}
|
|
10301
10461
|
|
|
10462
|
+
// Cheap cols/rows drift check — call before writing a new PTY chunk so
|
|
10463
|
+
// the chunk renders against the correct grid even if ResizeObserver
|
|
10464
|
+
// hasn't fired yet (e.g. mobile keyboard mid-animation, iOS PWA address
|
|
10465
|
+
// bar collapse, panel drag in progress). Only runs a real remeasure
|
|
10466
|
+
// when the container width changed since the last fit; otherwise it is
|
|
10467
|
+
// effectively a single offsetWidth read.
|
|
10468
|
+
function maybeRefitTerminal() {
|
|
10469
|
+
if (!state.terminal) return;
|
|
10470
|
+
var el = document.getElementById("output");
|
|
10471
|
+
if (!el) return;
|
|
10472
|
+
var w = el.offsetWidth;
|
|
10473
|
+
var h = el.offsetHeight;
|
|
10474
|
+
if (w === 0 || h === 0) return;
|
|
10475
|
+
// First call: just record the baseline and let ensureTerminalFit
|
|
10476
|
+
// (called from initTerminal/mount) own the initial sync.
|
|
10477
|
+
if (state.lastFitContainerWidth === undefined) {
|
|
10478
|
+
state.lastFitContainerWidth = w;
|
|
10479
|
+
state.lastFitContainerHeight = h;
|
|
10480
|
+
return;
|
|
10481
|
+
}
|
|
10482
|
+
if (w === state.lastFitContainerWidth && h === state.lastFitContainerHeight) return;
|
|
10483
|
+
state.lastFitContainerWidth = w;
|
|
10484
|
+
state.lastFitContainerHeight = h;
|
|
10485
|
+
var prevCols = state.terminal.cols;
|
|
10486
|
+
var prevRows = state.terminal.rows;
|
|
10487
|
+
if (typeof state.terminal.remeasure === "function") {
|
|
10488
|
+
state.terminal.remeasure();
|
|
10489
|
+
}
|
|
10490
|
+
if (state.terminal.cols !== prevCols || state.terminal.rows !== prevRows) {
|
|
10491
|
+
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
10492
|
+
// Don't replay here: the caller is about to write a fresh chunk and
|
|
10493
|
+
// a softResync would race with it. The chunk itself will reach the
|
|
10494
|
+
// correct grid; older buffer drift is repaired by the next
|
|
10495
|
+
// ensureTerminalFit / health check / manual refresh.
|
|
10496
|
+
}
|
|
10497
|
+
}
|
|
10498
|
+
|
|
10302
10499
|
function scheduleTerminalResize(immediate) {
|
|
10303
10500
|
if (state.resizeTimer) {
|
|
10304
10501
|
clearTimeout(state.resizeTimer);
|
|
@@ -10478,6 +10675,10 @@
|
|
|
10478
10675
|
// Fast path: write chunk directly to avoid full-output comparison.
|
|
10479
10676
|
state.lastChunkAt = Date.now();
|
|
10480
10677
|
state.terminalLiveStreamSessions[msg.sessionId] = true;
|
|
10678
|
+
// Detect cheap container-width drift before applying the chunk
|
|
10679
|
+
// so absolute-cursor CSI sequences in the chunk land on the
|
|
10680
|
+
// right grid (otherwise content tears or stacks at the top).
|
|
10681
|
+
maybeRefitTerminal();
|
|
10481
10682
|
state.terminal.write(msg.data.chunk);
|
|
10482
10683
|
state.terminalSessionId = msg.sessionId;
|
|
10483
10684
|
if (msg.data.output) {
|
|
@@ -2961,51 +2961,86 @@
|
|
|
2961
2961
|
|
|
2962
2962
|
.chat-overlay-controls {
|
|
2963
2963
|
position: absolute;
|
|
2964
|
-
top:
|
|
2965
|
-
right:
|
|
2964
|
+
top: 10px;
|
|
2965
|
+
right: 10px;
|
|
2966
2966
|
display: inline-flex;
|
|
2967
2967
|
align-items: center;
|
|
2968
|
-
gap: 6px;
|
|
2969
|
-
padding: 2px;
|
|
2970
|
-
border-radius: 7px;
|
|
2971
|
-
border: 1px solid rgba(125, 91, 57, 0.12);
|
|
2972
|
-
background: rgba(255, 250, 242, 0.72);
|
|
2973
|
-
backdrop-filter: blur(8px);
|
|
2974
|
-
box-shadow: 0 6px 18px rgba(89, 58, 32, 0.12);
|
|
2975
|
-
opacity: 0.84;
|
|
2976
|
-
transition: opacity 0.18s ease, background 0.18s ease, border-color 0.18s ease;
|
|
2977
2968
|
z-index: 12;
|
|
2978
2969
|
pointer-events: auto;
|
|
2970
|
+
opacity: 0;
|
|
2971
|
+
transform: translateY(-4px);
|
|
2972
|
+
transition: opacity 0.22s ease, transform 0.26s var(--ease-out-expo);
|
|
2979
2973
|
}
|
|
2980
2974
|
|
|
2981
2975
|
.chat-container:hover .chat-overlay-controls,
|
|
2982
2976
|
.chat-overlay-controls:focus-within {
|
|
2983
2977
|
opacity: 1;
|
|
2984
|
-
|
|
2985
|
-
border-color: rgba(197, 101, 61, 0.16);
|
|
2978
|
+
transform: translateY(0);
|
|
2986
2979
|
}
|
|
2987
2980
|
|
|
2988
2981
|
.chat-follow-toggle {
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2982
|
+
display: inline-flex;
|
|
2983
|
+
align-items: center;
|
|
2984
|
+
justify-content: center;
|
|
2985
|
+
min-width: unset;
|
|
2986
|
+
padding: 6px 11px;
|
|
2987
|
+
color: var(--text-tertiary);
|
|
2988
|
+
background: rgba(255, 250, 242, 0.7);
|
|
2989
|
+
backdrop-filter: blur(12px);
|
|
2990
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2991
|
+
border: 1px solid rgba(125, 91, 57, 0.1);
|
|
2992
|
+
border-radius: 999px;
|
|
2993
|
+
box-shadow: 0 1px 4px rgba(89, 58, 32, 0.06);
|
|
2994
|
+
cursor: pointer;
|
|
2995
|
+
transition: all 0.2s ease;
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
.chat-follow-toggle svg {
|
|
2999
|
+
flex-shrink: 0;
|
|
3000
|
+
opacity: 0.7;
|
|
3001
|
+
transition: opacity 0.18s ease, transform 0.2s var(--ease-out-expo);
|
|
2995
3002
|
}
|
|
2996
3003
|
|
|
2997
3004
|
.chat-follow-toggle.active {
|
|
2998
|
-
background: var(--accent-muted);
|
|
2999
3005
|
color: var(--accent);
|
|
3000
|
-
|
|
3006
|
+
background: rgba(255, 250, 242, 0.88);
|
|
3007
|
+
border-color: rgba(197, 101, 61, 0.18);
|
|
3008
|
+
box-shadow:
|
|
3009
|
+
0 1px 4px rgba(89, 58, 32, 0.08),
|
|
3010
|
+
0 0 0 1px rgba(197, 101, 61, 0.06);
|
|
3011
|
+
}
|
|
3012
|
+
|
|
3013
|
+
.chat-follow-toggle.active svg {
|
|
3014
|
+
opacity: 0.85;
|
|
3015
|
+
stroke: var(--accent);
|
|
3001
3016
|
}
|
|
3002
3017
|
|
|
3003
3018
|
.chat-follow-toggle:hover {
|
|
3004
|
-
|
|
3019
|
+
color: var(--text-primary);
|
|
3020
|
+
background: rgba(255, 252, 248, 0.92);
|
|
3021
|
+
border-color: rgba(125, 91, 57, 0.16);
|
|
3022
|
+
box-shadow: 0 2px 8px rgba(89, 58, 32, 0.1);
|
|
3023
|
+
}
|
|
3024
|
+
|
|
3025
|
+
.chat-follow-toggle:hover svg {
|
|
3026
|
+
opacity: 0.8;
|
|
3005
3027
|
}
|
|
3006
3028
|
|
|
3007
3029
|
.chat-follow-toggle.active:hover {
|
|
3008
|
-
|
|
3030
|
+
color: var(--accent-hover);
|
|
3031
|
+
background: rgba(255, 248, 240, 0.94);
|
|
3032
|
+
border-color: rgba(197, 101, 61, 0.24);
|
|
3033
|
+
box-shadow: 0 2px 8px rgba(184, 92, 55, 0.12);
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
.chat-follow-toggle.active:hover svg {
|
|
3037
|
+
stroke: var(--accent-hover);
|
|
3038
|
+
transform: translateY(1px);
|
|
3039
|
+
}
|
|
3040
|
+
|
|
3041
|
+
.chat-follow-toggle:active {
|
|
3042
|
+
transform: scale(0.94);
|
|
3043
|
+
transition-duration: 0.06s;
|
|
3009
3044
|
}
|
|
3010
3045
|
|
|
3011
3046
|
.chat-jump-bottom {
|
|
@@ -8127,6 +8162,7 @@
|
|
|
8127
8162
|
padding-top: 16px;
|
|
8128
8163
|
background: linear-gradient(180deg, rgba(255, 251, 245, 0), rgba(255, 251, 245, 0.98) 28px);
|
|
8129
8164
|
z-index: 1;
|
|
8165
|
+
justify-content: flex-end;
|
|
8130
8166
|
}
|
|
8131
8167
|
|
|
8132
8168
|
.settings-status-message {
|
|
@@ -9830,4 +9866,377 @@
|
|
|
9830
9866
|
opacity: 1;
|
|
9831
9867
|
}
|
|
9832
9868
|
|
|
9869
|
+
/* ============================================================ */
|
|
9870
|
+
/* Settings v2 — refined sections, file picker, toggle rows */
|
|
9871
|
+
/* ============================================================ */
|
|
9872
|
+
|
|
9873
|
+
/* 卡片头部(修改密码 / SSL 证书等) */
|
|
9874
|
+
.settings-card-head {
|
|
9875
|
+
display: flex;
|
|
9876
|
+
align-items: flex-start;
|
|
9877
|
+
gap: 12px;
|
|
9878
|
+
margin-bottom: 14px;
|
|
9879
|
+
}
|
|
9880
|
+
.settings-card-icon {
|
|
9881
|
+
flex: 0 0 auto;
|
|
9882
|
+
width: 36px;
|
|
9883
|
+
height: 36px;
|
|
9884
|
+
display: inline-flex;
|
|
9885
|
+
align-items: center;
|
|
9886
|
+
justify-content: center;
|
|
9887
|
+
border-radius: 10px;
|
|
9888
|
+
background: linear-gradient(135deg, rgba(197, 101, 61, 0.18), rgba(197, 101, 61, 0.06));
|
|
9889
|
+
color: var(--accent);
|
|
9890
|
+
font-size: 18px;
|
|
9891
|
+
box-shadow: inset 0 0 0 1px rgba(197, 101, 61, 0.18);
|
|
9892
|
+
}
|
|
9893
|
+
.settings-card-head-text {
|
|
9894
|
+
flex: 1;
|
|
9895
|
+
min-width: 0;
|
|
9896
|
+
display: flex;
|
|
9897
|
+
flex-direction: column;
|
|
9898
|
+
gap: 2px;
|
|
9899
|
+
}
|
|
9900
|
+
.settings-card-title {
|
|
9901
|
+
font-size: 0.9375rem;
|
|
9902
|
+
font-weight: 700;
|
|
9903
|
+
color: var(--text-primary);
|
|
9904
|
+
letter-spacing: 0.005em;
|
|
9905
|
+
line-height: 1.3;
|
|
9906
|
+
margin: 0;
|
|
9907
|
+
}
|
|
9908
|
+
.settings-card-desc {
|
|
9909
|
+
font-size: 0.75rem;
|
|
9910
|
+
color: var(--text-muted);
|
|
9911
|
+
line-height: 1.5;
|
|
9912
|
+
margin: 0;
|
|
9913
|
+
}
|
|
9914
|
+
.settings-card-actions {
|
|
9915
|
+
display: flex;
|
|
9916
|
+
justify-content: flex-end;
|
|
9917
|
+
align-items: center;
|
|
9918
|
+
gap: 10px;
|
|
9919
|
+
margin-top: 6px;
|
|
9920
|
+
}
|
|
9921
|
+
.settings-card-actions .btn {
|
|
9922
|
+
min-width: 120px;
|
|
9923
|
+
}
|
|
9924
|
+
.settings-success-message {
|
|
9925
|
+
color: var(--success);
|
|
9926
|
+
}
|
|
9927
|
+
|
|
9928
|
+
/* 区块头(关于 / 通知 tab 内的小段标题) */
|
|
9929
|
+
.settings-section-head {
|
|
9930
|
+
display: flex;
|
|
9931
|
+
align-items: flex-start;
|
|
9932
|
+
gap: 10px;
|
|
9933
|
+
margin-bottom: 12px;
|
|
9934
|
+
}
|
|
9935
|
+
.settings-section-icon {
|
|
9936
|
+
flex: 0 0 auto;
|
|
9937
|
+
width: 28px;
|
|
9938
|
+
height: 28px;
|
|
9939
|
+
display: inline-flex;
|
|
9940
|
+
align-items: center;
|
|
9941
|
+
justify-content: center;
|
|
9942
|
+
border-radius: 8px;
|
|
9943
|
+
background: rgba(197, 101, 61, 0.10);
|
|
9944
|
+
font-size: 14px;
|
|
9945
|
+
}
|
|
9946
|
+
.settings-section-head-text {
|
|
9947
|
+
flex: 1;
|
|
9948
|
+
min-width: 0;
|
|
9949
|
+
display: flex;
|
|
9950
|
+
flex-direction: column;
|
|
9951
|
+
gap: 2px;
|
|
9952
|
+
}
|
|
9953
|
+
.settings-section-heading {
|
|
9954
|
+
font-size: 0.875rem;
|
|
9955
|
+
font-weight: 700;
|
|
9956
|
+
color: var(--text-primary);
|
|
9957
|
+
letter-spacing: 0.005em;
|
|
9958
|
+
line-height: 1.3;
|
|
9959
|
+
margin: 0;
|
|
9960
|
+
}
|
|
9961
|
+
.settings-section-sub {
|
|
9962
|
+
font-size: 0.72rem;
|
|
9963
|
+
color: var(--text-muted);
|
|
9964
|
+
line-height: 1.5;
|
|
9965
|
+
margin: 0;
|
|
9966
|
+
}
|
|
9967
|
+
|
|
9968
|
+
/* Toggle 行(描述在左、开关在右) */
|
|
9969
|
+
.settings-toggle-row {
|
|
9970
|
+
display: flex;
|
|
9971
|
+
align-items: center;
|
|
9972
|
+
justify-content: space-between;
|
|
9973
|
+
gap: 14px;
|
|
9974
|
+
padding: 12px 14px;
|
|
9975
|
+
border-radius: 12px;
|
|
9976
|
+
background: rgba(255, 255, 255, 0.55);
|
|
9977
|
+
border: 1px solid rgba(125, 91, 57, 0.10);
|
|
9978
|
+
margin-top: 10px;
|
|
9979
|
+
}
|
|
9980
|
+
.settings-toggle-row + .settings-toggle-row,
|
|
9981
|
+
.settings-toggle-row + .settings-range-row,
|
|
9982
|
+
.settings-range-row + .settings-toggle-row {
|
|
9983
|
+
margin-top: 8px;
|
|
9984
|
+
}
|
|
9985
|
+
.settings-toggle-text {
|
|
9986
|
+
flex: 1;
|
|
9987
|
+
min-width: 0;
|
|
9988
|
+
display: flex;
|
|
9989
|
+
flex-direction: column;
|
|
9990
|
+
gap: 3px;
|
|
9991
|
+
}
|
|
9992
|
+
.settings-toggle-title {
|
|
9993
|
+
font-size: 0.8125rem;
|
|
9994
|
+
font-weight: 600;
|
|
9995
|
+
color: var(--text-primary);
|
|
9996
|
+
cursor: pointer;
|
|
9997
|
+
}
|
|
9998
|
+
.settings-toggle-desc {
|
|
9999
|
+
font-size: 0.72rem;
|
|
10000
|
+
color: var(--text-muted);
|
|
10001
|
+
line-height: 1.5;
|
|
10002
|
+
}
|
|
10003
|
+
.settings-switch {
|
|
10004
|
+
position: relative;
|
|
10005
|
+
flex: 0 0 auto;
|
|
10006
|
+
cursor: pointer;
|
|
10007
|
+
display: inline-block;
|
|
10008
|
+
}
|
|
10009
|
+
|
|
10010
|
+
/* Range 行(音量等) */
|
|
10011
|
+
.settings-range-row {
|
|
10012
|
+
display: flex;
|
|
10013
|
+
align-items: center;
|
|
10014
|
+
gap: 12px;
|
|
10015
|
+
padding: 12px 14px;
|
|
10016
|
+
border-radius: 12px;
|
|
10017
|
+
background: rgba(255, 255, 255, 0.55);
|
|
10018
|
+
border: 1px solid rgba(125, 91, 57, 0.10);
|
|
10019
|
+
margin-top: 8px;
|
|
10020
|
+
}
|
|
10021
|
+
.settings-range-label {
|
|
10022
|
+
font-size: 0.8125rem;
|
|
10023
|
+
font-weight: 600;
|
|
10024
|
+
color: var(--text-primary);
|
|
10025
|
+
flex: 0 0 auto;
|
|
10026
|
+
margin-bottom: 0;
|
|
10027
|
+
}
|
|
10028
|
+
.settings-range {
|
|
10029
|
+
flex: 1;
|
|
10030
|
+
min-width: 0;
|
|
10031
|
+
accent-color: var(--accent);
|
|
10032
|
+
}
|
|
10033
|
+
.settings-range-value {
|
|
10034
|
+
flex: 0 0 36px;
|
|
10035
|
+
text-align: right;
|
|
10036
|
+
font-size: 0.75rem;
|
|
10037
|
+
color: var(--text-secondary);
|
|
10038
|
+
font-variant-numeric: tabular-nums;
|
|
10039
|
+
}
|
|
10040
|
+
|
|
10041
|
+
/* 行内带操作的字段(select + 刷新等) */
|
|
10042
|
+
.settings-row-with-action {
|
|
10043
|
+
display: flex;
|
|
10044
|
+
gap: 8px;
|
|
10045
|
+
align-items: stretch;
|
|
10046
|
+
}
|
|
10047
|
+
.settings-row-with-action > .field-input,
|
|
10048
|
+
.settings-row-with-action > .field-select,
|
|
10049
|
+
.settings-row-with-action > select {
|
|
10050
|
+
flex: 1;
|
|
10051
|
+
min-width: 0;
|
|
10052
|
+
}
|
|
10053
|
+
.settings-row-with-action > .btn {
|
|
10054
|
+
flex-shrink: 0;
|
|
10055
|
+
}
|
|
10056
|
+
|
|
10057
|
+
/* settings-about-row 加按钮的变体 */
|
|
10058
|
+
.settings-about-row-action {
|
|
10059
|
+
flex-wrap: nowrap;
|
|
10060
|
+
}
|
|
10061
|
+
.settings-value-flex {
|
|
10062
|
+
flex: 1;
|
|
10063
|
+
text-align: right;
|
|
10064
|
+
}
|
|
10065
|
+
|
|
10066
|
+
/* ===== File picker ===== */
|
|
10067
|
+
.file-picker {
|
|
10068
|
+
position: relative;
|
|
10069
|
+
display: flex;
|
|
10070
|
+
align-items: center;
|
|
10071
|
+
gap: 12px;
|
|
10072
|
+
padding: 6px 12px 6px 6px;
|
|
10073
|
+
background: rgba(255, 255, 255, 0.72);
|
|
10074
|
+
border: 1px dashed rgba(125, 91, 57, 0.25);
|
|
10075
|
+
border-radius: 12px;
|
|
10076
|
+
transition: border-color 0.18s, background 0.18s;
|
|
10077
|
+
}
|
|
10078
|
+
.file-picker:hover {
|
|
10079
|
+
border-color: var(--accent-soft, rgba(197, 101, 61, 0.45));
|
|
10080
|
+
background: rgba(255, 255, 255, 0.9);
|
|
10081
|
+
}
|
|
10082
|
+
.file-picker.file-picker-has-file {
|
|
10083
|
+
border-style: solid;
|
|
10084
|
+
border-color: rgba(197, 101, 61, 0.35);
|
|
10085
|
+
background: linear-gradient(135deg, rgba(197, 101, 61, 0.08), rgba(255, 255, 255, 0.85));
|
|
10086
|
+
}
|
|
10087
|
+
.file-picker-input {
|
|
10088
|
+
position: absolute;
|
|
10089
|
+
width: 1px;
|
|
10090
|
+
height: 1px;
|
|
10091
|
+
opacity: 0;
|
|
10092
|
+
pointer-events: none;
|
|
10093
|
+
}
|
|
10094
|
+
.file-picker-trigger {
|
|
10095
|
+
display: inline-flex;
|
|
10096
|
+
align-items: center;
|
|
10097
|
+
gap: 6px;
|
|
10098
|
+
padding: 8px 14px;
|
|
10099
|
+
background: linear-gradient(135deg, var(--accent), color-mix(in srgb, var(--accent) 80%, #000 0%));
|
|
10100
|
+
color: #fff;
|
|
10101
|
+
font-size: 0.8125rem;
|
|
10102
|
+
font-weight: 600;
|
|
10103
|
+
border-radius: 9px;
|
|
10104
|
+
cursor: pointer;
|
|
10105
|
+
flex-shrink: 0;
|
|
10106
|
+
transition: filter 0.15s, transform 0.15s, box-shadow 0.15s;
|
|
10107
|
+
box-shadow: var(--shadow-xs);
|
|
10108
|
+
user-select: none;
|
|
10109
|
+
}
|
|
10110
|
+
.file-picker-trigger:hover {
|
|
10111
|
+
filter: brightness(1.05);
|
|
10112
|
+
transform: translateY(-1px);
|
|
10113
|
+
box-shadow: var(--shadow-sm);
|
|
10114
|
+
}
|
|
10115
|
+
.file-picker-trigger:active {
|
|
10116
|
+
transform: translateY(0);
|
|
10117
|
+
}
|
|
10118
|
+
.file-picker-input:focus-visible + .file-picker-trigger {
|
|
10119
|
+
outline: 2px solid var(--accent);
|
|
10120
|
+
outline-offset: 2px;
|
|
10121
|
+
}
|
|
10122
|
+
.file-picker-icon {
|
|
10123
|
+
width: 16px;
|
|
10124
|
+
height: 16px;
|
|
10125
|
+
flex-shrink: 0;
|
|
10126
|
+
}
|
|
10127
|
+
.file-picker-label {
|
|
10128
|
+
white-space: nowrap;
|
|
10129
|
+
}
|
|
10130
|
+
.file-picker-name {
|
|
10131
|
+
flex: 1;
|
|
10132
|
+
min-width: 0;
|
|
10133
|
+
font-size: 0.8125rem;
|
|
10134
|
+
color: var(--text-muted);
|
|
10135
|
+
overflow: hidden;
|
|
10136
|
+
text-overflow: ellipsis;
|
|
10137
|
+
white-space: nowrap;
|
|
10138
|
+
font-family: var(--font-mono);
|
|
10139
|
+
}
|
|
10140
|
+
.file-picker.file-picker-has-file .file-picker-name {
|
|
10141
|
+
color: var(--text-primary);
|
|
10142
|
+
font-weight: 500;
|
|
10143
|
+
}
|
|
10144
|
+
|
|
10145
|
+
/* ===== App icon picker (general tab) ===== */
|
|
10146
|
+
.settings-app-icon-block {
|
|
10147
|
+
margin-bottom: 18px;
|
|
10148
|
+
}
|
|
10149
|
+
.settings-app-icon-picker {
|
|
10150
|
+
display: flex;
|
|
10151
|
+
gap: 12px;
|
|
10152
|
+
flex-wrap: wrap;
|
|
10153
|
+
}
|
|
10154
|
+
.settings-app-icon-option {
|
|
10155
|
+
display: flex;
|
|
10156
|
+
flex-direction: column;
|
|
10157
|
+
align-items: center;
|
|
10158
|
+
gap: 6px;
|
|
10159
|
+
padding: 10px;
|
|
10160
|
+
border-radius: 14px;
|
|
10161
|
+
background: rgba(255, 255, 255, 0.55);
|
|
10162
|
+
border: 1px solid rgba(125, 91, 57, 0.12);
|
|
10163
|
+
cursor: pointer;
|
|
10164
|
+
transition: border-color 0.18s, background 0.18s, transform 0.18s, box-shadow 0.18s;
|
|
10165
|
+
min-width: 84px;
|
|
10166
|
+
}
|
|
10167
|
+
.settings-app-icon-option:hover {
|
|
10168
|
+
transform: translateY(-1px);
|
|
10169
|
+
border-color: rgba(197, 101, 61, 0.30);
|
|
10170
|
+
background: rgba(255, 255, 255, 0.78);
|
|
10171
|
+
box-shadow: var(--shadow-xs);
|
|
10172
|
+
}
|
|
10173
|
+
.settings-app-icon-option.selected,
|
|
10174
|
+
.settings-app-icon-option[aria-pressed="true"] {
|
|
10175
|
+
border-color: var(--accent);
|
|
10176
|
+
background: linear-gradient(135deg, rgba(197, 101, 61, 0.14), rgba(197, 101, 61, 0.04));
|
|
10177
|
+
box-shadow: inset 0 0 0 1px rgba(197, 101, 61, 0.20);
|
|
10178
|
+
}
|
|
10179
|
+
.settings-app-icon-preview {
|
|
10180
|
+
width: 56px;
|
|
10181
|
+
height: 56px;
|
|
10182
|
+
border-radius: 12px;
|
|
10183
|
+
overflow: hidden;
|
|
10184
|
+
background: var(--bg-tertiary);
|
|
10185
|
+
display: flex;
|
|
10186
|
+
align-items: center;
|
|
10187
|
+
justify-content: center;
|
|
10188
|
+
}
|
|
10189
|
+
.settings-app-icon-label {
|
|
10190
|
+
font-size: 0.72rem;
|
|
10191
|
+
color: var(--text-secondary);
|
|
10192
|
+
font-weight: 500;
|
|
10193
|
+
}
|
|
10194
|
+
|
|
10195
|
+
/* ===== Connect URL box refinements ===== */
|
|
10196
|
+
.settings-connect-url-text {
|
|
10197
|
+
font-family: var(--font-mono, monospace);
|
|
10198
|
+
font-size: 12px;
|
|
10199
|
+
color: var(--text-primary);
|
|
10200
|
+
word-break: break-all;
|
|
10201
|
+
user-select: all;
|
|
10202
|
+
-webkit-user-select: all;
|
|
10203
|
+
}
|
|
10204
|
+
|
|
10205
|
+
/* ===== Mobile tweaks ===== */
|
|
10206
|
+
@media (max-width: 640px) {
|
|
10207
|
+
.settings-toggle-row,
|
|
10208
|
+
.settings-range-row {
|
|
10209
|
+
padding: 10px 12px;
|
|
10210
|
+
}
|
|
10211
|
+
.settings-toggle-row {
|
|
10212
|
+
flex-wrap: wrap;
|
|
10213
|
+
}
|
|
10214
|
+
.settings-card-actions {
|
|
10215
|
+
justify-content: stretch;
|
|
10216
|
+
}
|
|
10217
|
+
.settings-card-actions .btn {
|
|
10218
|
+
flex: 1;
|
|
10219
|
+
min-width: 0;
|
|
10220
|
+
}
|
|
10221
|
+
.file-picker {
|
|
10222
|
+
flex-wrap: wrap;
|
|
10223
|
+
padding: 8px;
|
|
10224
|
+
}
|
|
10225
|
+
.file-picker-trigger {
|
|
10226
|
+
flex: 1 1 auto;
|
|
10227
|
+
justify-content: center;
|
|
10228
|
+
}
|
|
10229
|
+
.file-picker-name {
|
|
10230
|
+
flex: 1 1 100%;
|
|
10231
|
+
text-align: center;
|
|
10232
|
+
padding: 0 4px;
|
|
10233
|
+
}
|
|
10234
|
+
.settings-row-with-action {
|
|
10235
|
+
flex-wrap: wrap;
|
|
10236
|
+
}
|
|
10237
|
+
.settings-row-with-action > .btn {
|
|
10238
|
+
flex: 1;
|
|
10239
|
+
}
|
|
10240
|
+
}
|
|
10241
|
+
|
|
9833
10242
|
/* 结束标记 */
|