dsh-deepseek-style-theme 2.0.88 → 2.1.90
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -78
- package/lib/client.js +791 -857
- package/lib/index.js +143 -390
- package/package.json +5 -3
package/lib/client.js
CHANGED
|
@@ -14,6 +14,19 @@
|
|
|
14
14
|
// wants and the other entry is simply never asked for. The registrations are
|
|
15
15
|
// guarded individually because a loader might reject an id it did not request,
|
|
16
16
|
// and that must not take the working one down with it.
|
|
17
|
+
//
|
|
18
|
+
// Wrapped in an IIFE on purpose: this bundle is a CLASSIC script, so a top-level
|
|
19
|
+
// `const` lands in the global scope and a SECOND evaluation of the same file
|
|
20
|
+
// throws `Identifier 'dshDeepseekStyleThemeFactory' has already been declared`
|
|
21
|
+
// -- and it throws on the declaration line, before `__ModuleLoader__.load()`
|
|
22
|
+
// ever runs, so the new code never registers. The desktop app hot-reloads client
|
|
23
|
+
// plugin bundles on file change, which makes that the normal case rather than an
|
|
24
|
+
// edge case: from the second edit onward the page keeps the FIRST successful
|
|
25
|
+
// evaluation's JS (its styles stay in the DOM, so it still LOOKS themed) and
|
|
26
|
+
// silently ignores every later one. A full reload was the only way out. Inside
|
|
27
|
+
// the IIFE the name is function-scoped, every evaluation is independent, and a
|
|
28
|
+
// reload is no longer required.
|
|
29
|
+
(() => {
|
|
17
30
|
const dshDeepseekStyleThemeFactory = (require) => {
|
|
18
31
|
var module = { exports: {} };
|
|
19
32
|
var exports = module.exports;
|
|
@@ -135,7 +148,7 @@ const dshDeepseekStyleThemeFactory = (require) => {
|
|
|
135
148
|
// element and the { main, mainCanvas } handle explicitly.
|
|
136
149
|
//
|
|
137
150
|
// DOM hooks the ported code still reads (product/platform markup, intentionally
|
|
138
|
-
// NOT renamed): `[data-composer-card]` and `[data-
|
|
151
|
+
// NOT renamed): `[data-composer-card]` and `[data-composer-stats]` inside
|
|
139
152
|
// visualRect(), and the `button` closest-match in attachFluidInteractions.
|
|
140
153
|
//
|
|
141
154
|
// Indentation note: this fragment is tab-indented at a base of 2 tabs to match
|
|
@@ -944,14 +957,14 @@ void main() {
|
|
|
944
957
|
return target instanceof Element ? target.closest(SPOT_SELECTOR) : null;
|
|
945
958
|
}
|
|
946
959
|
// Inlined from upstream `src/client/spot-core.ts` (lines 1717-1729). The
|
|
947
|
-
// `[data-composer-card]` / `[data-
|
|
960
|
+
// `[data-composer-card]` / `[data-composer-stats]` selectors are product markup
|
|
948
961
|
// and are deliberately left unrenamed; a mount that lacks them simply
|
|
949
962
|
// falls back to the spot's own border box.
|
|
950
963
|
function visualRect(spot) {
|
|
951
964
|
if (spot.querySelector("[data-composer-card]") !== null) {
|
|
952
965
|
const card = spot.querySelector("[data-composer-card]");
|
|
953
966
|
const r0 = card.getBoundingClientRect();
|
|
954
|
-
const stats = spot.querySelector("[data-
|
|
967
|
+
const stats = spot.querySelector("[data-composer-stats]");
|
|
955
968
|
if (stats === null) return r0;
|
|
956
969
|
const r1 = stats.getBoundingClientRect();
|
|
957
970
|
const left = Math.min(r0.left, r1.left);
|
|
@@ -1092,12 +1105,19 @@ void main() {
|
|
|
1092
1105
|
// `[role=menu]`, `.dshome-*`). No product class names — always injected.
|
|
1093
1106
|
// Color model: `data-dshome-color` ∈ {green(base), red(鲜红 peak), blue}
|
|
1094
1107
|
// re-tints every var(--ds-brand*) consumer.
|
|
1095
|
-
const CORE_CSS =
|
|
1096
|
-
|
|
1108
|
+
const CORE_CSS = `:root{--ds-brand:#059669;--ds-brand-deep:#047857;--ds-brand-mid:#10B981;--ds-brand-light:#34D399;--ds-glass-blur:24px}
|
|
1109
|
+
/* 圆角策略 = DSTT 原设计,别自己发明数值:
|
|
1110
|
+
产品 corner-shape.css 已用 @supports(corner-shape:superellipse(1.5)) 对 *、:before、:after
|
|
1111
|
+
统一设了 superellipse(1.5),本主题**保留**它,只在胶囊/正圆这类会被超级椭圆压变形的
|
|
1112
|
+
元素上改回真圆——产品自己的文档也要求这样配对("full-round shapes — border-radius:50%
|
|
1113
|
+
circles and pill radii — pair corner-shape:round with their radius")。
|
|
1114
|
+
历史上这个覆盖写在 .dshome-locale / .wSkVaW_tab 上,2.1.0 语义化后集中到下面那一处,
|
|
1115
|
+
覆盖中/EN 切换、标签、header 里各胶囊与圆形按钮。 */
|
|
1097
1116
|
html,body{background-color:transparent!important}
|
|
1098
1117
|
/* THE LABEL TOKENS ARE NOT PINNED HERE, AND MUST NOT BE AGAIN.
|
|
1099
1118
|
A previous attempt at the invisible-text bug declared
|
|
1100
|
-
body{--dsw-alias-label-primary:...!important}
|
|
1119
|
+
body{--dsw-alias-label-primary:...!important}
|
|
1120
|
+
under our own scheme marker,
|
|
1101
1121
|
so that background and text could never end up on opposite sides. It did fix
|
|
1102
1122
|
that, and it broke something worse: those !important declarations outrank the
|
|
1103
1123
|
PRODUCT's tokens, so when the user switched appearance the product changed its
|
|
@@ -1209,19 +1229,16 @@ body[data-dshome-dark] .dshome-dstt-status[data-peak=false]{color:var(--ds-brand
|
|
|
1209
1229
|
.dshome-dstt-input-state[data-ok=false]{color:var(--dsw-alias-state-error-primary)}
|
|
1210
1230
|
.dshome-dstt-seg-row{margin-top:8px;flex-wrap:wrap}
|
|
1211
1231
|
.dshome-dstt-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 45%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-warn-primary) 10%,transparent)}
|
|
1212
|
-
.dshome-dstt-warn .dshome-dstt-row-title{color:var(--dsw-alias-state-warn-primary)}
|
|
1213
|
-
`;
|
|
1232
|
+
.dshome-dstt-warn .dshome-dstt-row-title{color:var(--dsw-alias-state-warn-primary)}`;
|
|
1214
1233
|
|
|
1215
1234
|
// Locale toggle styling. The two header-hover dependent rules live in the
|
|
1216
1235
|
// `.wSkVaW_root` patch block below, so the toggle stays styled (short
|
|
1217
1236
|
// labels) even if the header class renames.
|
|
1218
|
-
//
|
|
1219
|
-
//
|
|
1220
|
-
// rectangle; `corner-shape:round` restores true round ends here.
|
|
1237
|
+
// corner-shape 见 CORE_CSS 顶部那段说明:产品全局是 superellipse(1.5),胶囊与正圆
|
|
1238
|
+
// 必须配对 `round`,否则会被超级椭圆压成方圆。这一条就是那个配对(DSTT 原设计)。
|
|
1221
1239
|
// Label bloom: the short 中/E label grows into 中文/EN with an animated
|
|
1222
1240
|
// max-width instead of the old `display` hard swap.
|
|
1223
|
-
const ACTIONS_CSS =
|
|
1224
|
-
.dshome-locale{display:inline-flex;align-items:center;height:28px;border:1px solid color-mix(in srgb,var(--ds-brand) 25%,transparent);border-radius:999px;background:color-mix(in srgb,var(--ds-brand) 8%,transparent);overflow:hidden;flex:none;corner-shape:round;transition:transform .28s cubic-bezier(.2,.8,.2,1),box-shadow .28s ease,border-color .22s ease,background .22s ease}
|
|
1241
|
+
const ACTIONS_CSS = `.dshome-locale{display:inline-flex;align-items:center;height:28px;border:1px solid color-mix(in srgb,var(--ds-brand) 25%,transparent);border-radius:999px;background:color-mix(in srgb,var(--ds-brand) 8%,transparent);overflow:hidden;flex:none;corner-shape:round;transition:transform .28s cubic-bezier(.2,.8,.2,1),box-shadow .28s ease,border-color .22s ease,background .22s ease}
|
|
1225
1242
|
.dshome-locale:hover{transform:translateY(-1px) scale(1.03);border-color:color-mix(in srgb,var(--ds-brand) 48%,transparent);background:color-mix(in srgb,var(--ds-brand) 14%,transparent);box-shadow:0 4px 14px color-mix(in srgb,var(--ds-brand) 26%,transparent)}
|
|
1226
1243
|
body[data-dshome-dark] .dshome-locale{border-color:color-mix(in srgb,var(--ds-brand) 35%,transparent);background:color-mix(in srgb,var(--ds-brand) 16%,transparent)}
|
|
1227
1244
|
body[data-dshome-dark] .dshome-locale:hover{border-color:color-mix(in srgb,var(--ds-brand) 55%,transparent);background:color-mix(in srgb,var(--ds-brand) 24%,transparent);box-shadow:0 4px 16px color-mix(in srgb,var(--ds-brand) 34%,transparent)}
|
|
@@ -1235,17 +1252,20 @@ body[data-dshome-dark] .dshome-locale:hover{border-color:color-mix(in srgb,var(-
|
|
|
1235
1252
|
.dshome-locale .dshome-full{max-width:0;opacity:0;transform:translateX(-6px)}
|
|
1236
1253
|
body[data-dshome-dark] .dshome-locale button{color:var(--ds-brand-light)}
|
|
1237
1254
|
body[data-dshome-dark] .dshome-locale button:not(.dshome-on):hover{background:color-mix(in srgb,var(--ds-brand) 22%,transparent);color:#fff}
|
|
1238
|
-
body[data-dshome-dark] .dshome-locale button.dshome-on{background:linear-gradient(135deg,color-mix(in srgb,var(--ds-brand) 72%,#fff),var(--ds-brand));color:#fff}
|
|
1239
|
-
`;
|
|
1255
|
+
body[data-dshome-dark] .dshome-locale button.dshome-on{background:linear-gradient(135deg,color-mix(in srgb,var(--ds-brand) 72%,#fff),var(--ds-brand));color:#fff}`;
|
|
1240
1256
|
|
|
1241
|
-
const SUBAGENT_PANEL_CSS =
|
|
1242
|
-
.dshome-sa{position:fixed;right:16px;bottom:16px;z-index:70;width:286px;max-width:calc(100vw - 32px);box-sizing:border-box;display:flex;flex-direction:column;gap:2px;padding:10px 12px 8px;border-radius:16px;border:1px solid rgba(255,255,255,.55);background:linear-gradient(165deg,rgba(255,255,255,.46),rgba(255,255,255,.22));box-shadow:0 12px 40px rgba(13,30,60,.14),inset 0 1px 0 rgba(255,255,255,.85),inset 0 0 0 .5px rgba(255,255,255,.5);-webkit-backdrop-filter:blur(24px) saturate(1.6);backdrop-filter:blur(24px) saturate(1.6);font-family:inherit;transition:opacity .22s ease,transform .22s ease;animation:dshome-sa-in .3s cubic-bezier(.2,.8,.2,1)}
|
|
1257
|
+
const SUBAGENT_PANEL_CSS = `.dshome-sa{position:fixed;right:16px;bottom:16px;z-index:70;width:286px;max-width:calc(100vw - 32px);box-sizing:border-box;display:flex;flex-direction:column;gap:2px;padding:10px 12px 8px;border-radius:16px;border:1px solid rgba(255,255,255,.55);background:linear-gradient(165deg,rgba(255,255,255,.46),rgba(255,255,255,.22));box-shadow:0 12px 40px rgba(13,30,60,.14),inset 0 1px 0 rgba(255,255,255,.85),inset 0 0 0 .5px rgba(255,255,255,.5);-webkit-backdrop-filter:blur(24px) saturate(1.6);backdrop-filter:blur(24px) saturate(1.6);font-family:inherit;transition:opacity .22s ease,transform .22s ease;animation:dshome-sa-in .3s cubic-bezier(.2,.8,.2,1)}
|
|
1243
1258
|
.dshome-sa[hidden]{display:none!important}
|
|
1244
1259
|
.dshome-sa.dshome-sa-leave{opacity:0;transform:translateY(10px);pointer-events:none}
|
|
1245
1260
|
body[data-dshome-dark] .dshome-sa{background:linear-gradient(165deg,rgba(21,42,86,.52),rgba(10,26,58,.3));border-color:color-mix(in srgb,var(--ds-brand) 24%,transparent);box-shadow:0 12px 40px rgba(0,0,0,.4),inset 0 1px 0 rgba(255,255,255,.12),inset 0 0 0 .5px color-mix(in srgb,var(--ds-brand) 16%,transparent)}
|
|
1246
|
-
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.dshome-sa{background:linear-gradient(165deg,rgba(255,255,255,.84),rgba(255,255,255,.68))}
|
|
1261
|
+
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){.dshome-sa{background:linear-gradient(165deg,rgba(255,255,255,.84),rgba(255,255,255,.68))}
|
|
1262
|
+
body[data-dshome-dark] .dshome-sa{background:linear-gradient(165deg,rgba(13,30,60,.88),rgba(10,26,58,.78))}}
|
|
1247
1263
|
@keyframes dshome-sa-in{from{opacity:0;transform:translateY(14px) scale(.97)}to{opacity:1;transform:none}}
|
|
1248
1264
|
.dshome-sa-head{display:flex;flex-direction:row;align-items:center;gap:7px;min-height:26px}
|
|
1265
|
+
.dshome-sa-grab{cursor:grab;touch-action:none;user-select:none;-webkit-user-select:none}
|
|
1266
|
+
.dshome-sa-grab:active{cursor:grabbing}
|
|
1267
|
+
.dshome-sa-dragging{transition:none!important;animation:none!important;box-shadow:0 18px 52px rgba(13,30,60,.22),inset 0 1px 0 rgba(255,255,255,.9),inset 0 0 0 .5px rgba(255,255,255,.5)!important}
|
|
1268
|
+
.dshome-sa-dragging .dshome-sa-list{pointer-events:none}
|
|
1249
1269
|
.dshome-sa-pulse{position:relative;flex:none;width:8px;height:8px;border-radius:50%;background:var(--ds-brand);animation:dshome-sa-pulse 1.8s ease-out infinite}
|
|
1250
1270
|
@keyframes dshome-sa-pulse{0%{box-shadow:0 0 0 0 color-mix(in srgb,var(--ds-brand) 45%,transparent)}70%{box-shadow:0 0 0 7px color-mix(in srgb,var(--ds-brand) 0%,transparent)}100%{box-shadow:0 0 0 0 color-mix(in srgb,var(--ds-brand) 0%,transparent)}}
|
|
1251
1271
|
.dshome-sa-title{flex:1;min-width:0;font-size:12.5px;line-height:18px;font-weight:600;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
@@ -1284,8 +1304,7 @@ body[data-dshome-dark] .dshome-sa-item.dshome-sa-stopping .dshome-sa-bar{backgro
|
|
|
1284
1304
|
.dshome-sa-bar{position:relative;height:3px;border-radius:999px;background:color-mix(in srgb,var(--ds-brand) 12%,transparent);overflow:hidden}
|
|
1285
1305
|
.dshome-sa-bar-fill{position:absolute;inset:0;width:42%;border-radius:999px;background:linear-gradient(90deg,transparent,var(--ds-brand),transparent);animation:dshome-sa-slide 1.5s ease-in-out infinite}
|
|
1286
1306
|
@keyframes dshome-sa-slide{0%{transform:translateX(-110%)}100%{transform:translateX(350%)}}
|
|
1287
|
-
@media (max-width:760px){.dshome-sa{right:10px;bottom:10px;width:min(286px,calc(100vw - 20px))}}
|
|
1288
|
-
`;
|
|
1307
|
+
@media (max-width:760px){.dshome-sa{right:10px;bottom:10px;width:min(286px,calc(100vw - 20px))}}`;
|
|
1289
1308
|
|
|
1290
1309
|
// ── USER_BUBBLE: the one selector both recipes glass user bubbles with ────
|
|
1291
1310
|
// Declared HERE, above PATCH_BLOCKS and GLASS_CSS, because both interpolate
|
|
@@ -1321,7 +1340,40 @@ body[data-dshome-dark] .dshome-sa-item.dshome-sa-stopping .dshome-sa-bar{backgro
|
|
|
1321
1340
|
// still working sits outside any flow-kind container until the turn picks it
|
|
1322
1341
|
// up — is knowingly left unglazed for that moment: a transient unglazed
|
|
1323
1342
|
// bubble is a cosmetic wait, a teleporting button is not.
|
|
1324
|
-
|
|
1343
|
+
// 结构面的语义判据(0.2.0 起):产品没给侧栏外壳这类容器任何 data-*,
|
|
1344
|
+
// 但它们的**组件 key 是稳定身份**(_newSession / _brand / _scrollBody /
|
|
1345
|
+
// _sessionRow / _bubble),每次构建变的只是前面的哈希。所以按唯一后缀匹配,
|
|
1346
|
+
// 并用 :has() 守卫——产品把 key 也改名时选择器匹配不到元素,结果是该面没有
|
|
1347
|
+
// 样式,而不是套到错误的元素上。(0.2.0 实测这些后缀各只有 1–2 个取值。)
|
|
1348
|
+
// 侧栏外壳的认法。2026-10-03 实测:官方桌面 0.2.0-rc.2 的侧栏根是
|
|
1349
|
+
// `<div class="_2H3hWW_root">`(产品 JSX:jsxs("div", { className: clsx(SidebarRoot.root, …) })),
|
|
1350
|
+
// **既不是 aside 也不是 [role="navigation"]** —— 原来那半支因此 0 命中,
|
|
1351
|
+
// 侧栏玻璃就整块没上(探针里"侧栏外壳: 0")。
|
|
1352
|
+
// 现在两支并列写进一个 :is(),保持"一个复合选择器、没有顶层逗号"这条底线:
|
|
1353
|
+
// 顶层逗号会在 `html[data-dshome-glass="liquid"] :is(SIDEBAR){…}` 这种前缀用法里
|
|
1354
|
+
// 让逗号后面的分支逃出闸门。
|
|
1355
|
+
// · 第 1 支:老写作,留着不亏(产品哪天改回语义标签就用上);
|
|
1356
|
+
// · 第 2 支:直接装着 _logoRow 的那个 div,并且内部有 _newSession。
|
|
1357
|
+
// `_logoRow` 在 JSX 里是 root 的直接子元素(children 数组的第一项之一),
|
|
1358
|
+
// 所以 `>` 成立;两个条件一起用,避免命中别的容器。
|
|
1359
|
+
const SIDEBAR = ':is(:is(aside,[role="navigation"]):has(:is([class*="_newSession"],[class*="_brand"])),div:has(> [class*="_logoRow"]):has([class*="_newSession"]))';
|
|
1360
|
+
// 末尾那道按形状排掉 tooltip 的守卫(:not([class*="_bubble_"]))必须留着。
|
|
1361
|
+
// 它是 2.0.90 用一次事故换来的:产品的 tooltip 类名是 `_bubble_<hash>_<n>`
|
|
1362
|
+
// (前导下划线那种写法),会被 [class*="_bubble"] 一起吃进来;而主题给气泡的
|
|
1363
|
+
// `position:relative` 会把 position:fixed 的浮层打成普通流内块,于是悬停发送/
|
|
1364
|
+
// 复制按钮时按钮被从光标底下顶走、tooltip 卸载、按钮回来、循环往复 —— 用户看到的
|
|
1365
|
+
// 「按钮跳上跳下」。2.1.0 语义化时这道守卫丢了,只剩 [role="tooltip"],而角色不一定
|
|
1366
|
+
// 挂在我们认识的那一层上。消息气泡是 `<hash>_bubble`(下划线结尾),不含 `_bubble_`,
|
|
1367
|
+
// 所以这道守卫不会误伤它们。
|
|
1368
|
+
const USER_BUBBLE = ':is([data-chat-flow-kind="user"],[data-chat-flow-kind="steering"]) [class*="_bubble"]:not([class*="_bubble_"]):not([role="tooltip"])';
|
|
1369
|
+
// 输入区那块玻璃面 = **输入卡自己**。
|
|
1370
|
+
// 2026-10-03 试过两个别的落点,都被实测否掉:
|
|
1371
|
+
// · data-composer-stats 的父元素:display:contents 空壳,盒子 0×0,玻璃画上去等于没画;
|
|
1372
|
+
// · 卡片的父元素:实测 418×158 —— 左右各多 16px 内边距、下面还多出统计行所在的
|
|
1373
|
+
// dock,用户看到的就是「对话框被撑大了」,而且连鼠标随动的倾斜也一起摊到那一大块上。
|
|
1374
|
+
// 所以玻璃、倾斜、光标高光一律认卡片自己(实测 386×98,与产品原样同尺寸),
|
|
1375
|
+
// 并且**不动产品自己的圆角、边框、内边距**,只换底色与模糊。
|
|
1376
|
+
const COMPOSER_PLATE = '[data-composer-card]';
|
|
1325
1377
|
|
|
1326
1378
|
// PATCH_BLOCKS: every rule group that targets product build-time class
|
|
1327
1379
|
// names. `anchor` is the class that must exist in the DOM before the
|
|
@@ -1329,248 +1381,155 @@ body[data-dshome-dark] .dshome-sa-item.dshome-sa-stopping .dshome-sa-bar{backgro
|
|
|
1329
1381
|
// renames in a product rebuild, and `anchor` may be a list of
|
|
1330
1382
|
// alternatives when a surface has already been renamed once.
|
|
1331
1383
|
const PATCH_BLOCKS = [
|
|
1332
|
-
//
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1384
|
+
// 首屏:**字号交还官方**(需求第 8 条:明暗主题与字号都借官方接口,不自建)。
|
|
1385
|
+
// 原来这里挂着两条 font-size:34px 的标题规则,锚点是 [class*="composerHero"]。
|
|
1386
|
+
// 2026-10-03 实测该后缀在当前产品里 0 命中(首屏那一家是 Hqq-bq_headline),
|
|
1387
|
+
// 也就是说它们**早就是死的**——删掉没有任何视觉变化,只是把死代码清掉。
|
|
1388
|
+
// 预览角标那条保留:它是样式不是字号,而且 README 的语义钩子表里本来写的就是
|
|
1389
|
+
// [class*="_previewBadge"],没有 composerHero 那一层——这里让代码跟上文档。
|
|
1390
|
+
{ anchor: '[class*="_previewBadge"], [class*="_fish"]', css: `
|
|
1391
|
+
:is([class*="_previewBadge"]){border-radius:999px!important;background:color-mix(in srgb,var(--ds-brand) 10%,transparent)!important;border:1px solid color-mix(in srgb,var(--ds-brand) 25%,transparent)!important;color:var(--ds-brand)!important}
|
|
1392
|
+
:is([class*="_fish"]){color:var(--ds-brand)!important}
|
|
1337
1393
|
` },
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1394
|
+
{ anchor: "[data-composer-card]", css: `
|
|
1395
|
+
/* 输入卡玻璃化。2026-10-03 两次实测后的结论,玻璃只画在**输入卡自己**身上:
|
|
1396
|
+
· 画在 data-composer-stats 的父元素上 —— 那是 display:contents 空壳,盒子 0×0,
|
|
1397
|
+
等于没画,输入卡一直保持产品自己的纯白;
|
|
1398
|
+
· 画在卡片的父元素上 —— 那是 418×158:左右各多 16px 内边距、下面还多出统计行
|
|
1399
|
+
所在的 dock,看上去就是「对话框被撑大了」,连倾斜也摊到那一大块上。
|
|
1400
|
+
卡片自己是 386×98,与产品原样同尺寸。这里**不动产品的圆角、边框、内边距**,
|
|
1401
|
+
只换底色与模糊,所以尺寸与位置一个像素都不变。
|
|
1402
|
+
本段在模板字符串里,注释里不能出现反引号。 */
|
|
1403
|
+
${COMPOSER_PLATE}{background:rgba(255,255,255,.42)!important;-webkit-backdrop-filter:blur(28px) saturate(1.42) brightness(1.02)!important;backdrop-filter:blur(28px) saturate(1.42) brightness(1.02)!important;box-shadow:0 8px 32px rgba(13,30,60,.1),inset 0 1px 1px hsla(0,0%,100%,.8)}
|
|
1404
|
+
body[data-dshome-dark] ${COMPOSER_PLATE}{background:rgba(13,30,60,.55)!important;box-shadow:0 10px 40px rgba(0,0,0,.35),inset 0 1px 1px rgba(255,255,255,.06)}
|
|
1348
1405
|
` },
|
|
1349
|
-
//
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
.
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1406
|
+
// 结构面:产品没给这些面语义钩子,但它们的**组件 key 是稳定身份**
|
|
1407
|
+
// (_newSession/_brand/_scrollBody/_sessionRow/_bubble…),每次构建变的
|
|
1408
|
+
// 只是前面的哈希。所以按「唯一后缀」匹配,并用 :has() 守卫:
|
|
1409
|
+
// 一旦产品把这些 key 也改了,选择器匹配不到任何元素 —— 结果是该面
|
|
1410
|
+
// 没有样式,而不是套到错误的元素上。0.2.0 实测这些后缀各只有 1–2 个
|
|
1411
|
+
// 取值,不存在撞名。
|
|
1412
|
+
{ anchor: '[class*="_newSession"], [class*="_scrollBody"], [class*="_composerCard"]', css: `
|
|
1413
|
+
:is(${SIDEBAR}){position:relative;background:transparent!important}
|
|
1414
|
+
:is(${SIDEBAR})::before{content:"";position:absolute;inset:0;z-index:-1;background:rgba(255,255,255,.5);-webkit-backdrop-filter:blur(var(--ds-glass-blur));backdrop-filter:blur(var(--ds-glass-blur));border-right:1px solid rgba(0,0,0,.06);pointer-events:none}
|
|
1415
|
+
body[data-dshome-dark] :is(${SIDEBAR})::before{background:rgba(10,26,58,.48);border-right:1px solid rgba(255,255,255,.08)}
|
|
1416
|
+
:is([class*="_brand"]){color:var(--ds-brand);cursor:pointer}
|
|
1417
|
+
body[data-dshome-dark] :is([class*="_brand"]){color:#fff}
|
|
1418
|
+
:where(div):has(> [class*="_overlayLayer"]){background:transparent!important}
|
|
1419
|
+
:where(div):has(> [class*="_overlayLayer"]) [class*="_sidebarCol"]{border-right:none!important}
|
|
1420
|
+
:is([class*="_sessionRow"],[class*="_projectRow"]){background:rgba(255,255,255,.35)!important;border:1px solid transparent;border-radius:10px!important;margin:1px 0;transition:background .15s ease,border-color .15s ease,transform .15s ease}
|
|
1421
|
+
:is([class*="_sessionRow"],[class*="_projectRow"]):hover{background:rgba(255,255,255,.6)!important;-webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);transform:translateX(2px)}
|
|
1422
|
+
body[data-dshome-dark] :is([class*="_sessionRow"],[class*="_projectRow"]){background:rgba(21,42,86,.4)!important}
|
|
1423
|
+
body[data-dshome-dark] :is([class*="_sessionRow"],[class*="_projectRow"]):hover{background:rgba(30,52,100,.6)!important}
|
|
1424
|
+
:where(div):has(> [class*="_scrollBody"]){position:relative;background:transparent!important}
|
|
1425
|
+
:is([class*="_scrollBody"]){padding-top:56px}
|
|
1426
|
+
:is([class*="_scrollBody"]) [class*="_viewArea"]{padding:0 18px}
|
|
1359
1427
|
` },
|
|
1360
|
-
|
|
1361
|
-
//
|
|
1362
|
-
//
|
|
1363
|
-
//
|
|
1364
|
-
//
|
|
1365
|
-
//
|
|
1366
|
-
//
|
|
1367
|
-
//
|
|
1368
|
-
//
|
|
1369
|
-
//
|
|
1370
|
-
//
|
|
1371
|
-
//
|
|
1372
|
-
//
|
|
1373
|
-
//
|
|
1374
|
-
{
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
body[data-dshome-dark]
|
|
1408
|
-
body[data-dshome-dark] .wSkVaW_tab:hover{background:color-mix(in srgb,var(--ds-brand) 24%,transparent);box-shadow:0 4px 14px color-mix(in srgb,var(--ds-brand) 34%,transparent)}
|
|
1409
|
-
body[data-dshome-dark] .wSkVaW_tabActive{box-shadow:0 3px 12px color-mix(in srgb,var(--ds-brand) 40%,transparent)}
|
|
1428
|
+
// 会话标题栏:风格、玻璃、按钮、悬停一律恢复成原来的样子(需求第 9 条 + "另有"),
|
|
1429
|
+
// 而**切换本身完全归官方**(第 7 条)—— 我们只画,不接管点击,也不碰 active。
|
|
1430
|
+
//
|
|
1431
|
+
// 2.0.90 里这一整套是写死 `.wSkVaW_*` 的,语义化时整块丢了。丢掉的不只是样子:
|
|
1432
|
+
// 其中 z-index 那条修的是**真 bug** —— 产品的列宽抓手是 40px 宽、满高、
|
|
1433
|
+
// z-index 8 的条,标题栏默认压在它下面,于是光标落在标题栏自己的盒子里也会
|
|
1434
|
+
// 变成 col-resize,栏里的控件点不到。9 把栏提到抓手之上,抓手在栏之外照旧可抓。
|
|
1435
|
+
//
|
|
1436
|
+
// 认法(全部按结构,不写哈希):
|
|
1437
|
+
// · 标题栏 = 直接子元素里有 _headerLeading、且内部有 _headerActions 的那一层。
|
|
1438
|
+
// 侧栏那块 _sectionHeader 有 headerActions 但没有 headerLeading,不会误伤。
|
|
1439
|
+
// · 标签页 = 同一层里的 _tabs;单个标签 = _tab / _tabActive;路径 = _crumb。
|
|
1440
|
+
// 实测结构(2026-10-06,0.2.0-rc.2):
|
|
1441
|
+
// _header (56,0,744,76, position:static)
|
|
1442
|
+
// ├ _headerLeading > div > { _titleRow, _tabs } ← 标签页在第二行 y=50
|
|
1443
|
+
// └ div { _headerActions, _crumbs, _moreButton, _label, _split }
|
|
1444
|
+
// 原设计是 36px 单行胶囊,靠 titleRow{display:contents} 把两层压平成一行;
|
|
1445
|
+
// 这里照做,否则胶囊会变成 76px 高的两行块。
|
|
1446
|
+
{ anchor: '[class*="_headerLeading"]', css: `
|
|
1447
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]){position:absolute;top:8px;left:24px;right:24px;z-index:9;min-height:36px;padding:4px 12px;display:flex;flex-direction:row;align-items:center;gap:8px;justify-content:flex-start;border:0!important;border-radius:0;background:transparent!important;transition:border-radius .3s ease}
|
|
1448
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"])::before{content:"";position:absolute;inset:0;z-index:-1;background:transparent;border:1px solid transparent;border-radius:inherit;-webkit-backdrop-filter:none;backdrop-filter:none;transition:background .3s ease,border-color .3s ease,box-shadow .3s ease}
|
|
1449
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover{border-radius:999px}
|
|
1450
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover::before{background:rgba(255,255,255,.45);border-color:rgba(0,0,0,.06);-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);box-shadow:0 4px 16px rgba(0,0,0,.05)}
|
|
1451
|
+
body[data-dshome-dark] :has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover::before{background:rgba(13,30,60,.45);border-color:rgba(255,255,255,.1);box-shadow:0 4px 20px rgba(0,0,0,.3)}
|
|
1452
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"])::after{display:none!important}
|
|
1453
|
+
/* 压平:两层包装 + titleRow 一律 display:contents,让所有控件成为标题栏这一条 flex 的直接子项,
|
|
1454
|
+
于是 order 才排得动。少了这一步,标签页会留在第二行、胶囊变成两行高的块。 */
|
|
1455
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) > :is([class*="_headerLeading"]){display:contents}
|
|
1456
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) > :is([class*="_headerLeading"]) > div{display:contents}
|
|
1457
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_titleRow"],[class*="_titleCluster"]){display:contents}
|
|
1458
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_crumb"]){order:2;flex:none;min-width:0;border-radius:999px}
|
|
1459
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_tabs"]){order:0;flex:none;align-items:center;display:none;gap:8px;margin:0;padding:0}
|
|
1460
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_headerActions"]){order:1;flex:none;align-items:center;display:flex;gap:8px}
|
|
1461
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_headerUtilities"]){order:3;flex:none;align-items:center;display:flex;gap:8px;margin-left:auto}
|
|
1462
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_headerCorner"]){order:4;flex:none;align-items:center;display:flex}
|
|
1463
|
+
/* 标签页"悬停标题栏才出现":原样保留,但驱动它的是纯 CSS :hover,不再是那个 JS 类
|
|
1464
|
+
(dshome-swap 随 §3.7 的接管一起删掉了)。active 由产品给,我们只画。 */
|
|
1465
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover :is([class*="_tabs"]){display:flex;animation:dshome-tab-in .22s ease}
|
|
1466
|
+
/* 标签页胶囊 */
|
|
1467
|
+
:is([class*="_tab"]):not([class*="_tabs"]):not([class*="_tabStrip"]):not([class*="_tabLayout"]){border:1px solid color-mix(in srgb,var(--ds-brand) 25%,transparent);background:color-mix(in srgb,var(--ds-brand) 8%,transparent);border-radius:999px;corner-shape:round;padding:5px 16px;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:16px;font-weight:500;transition:color .22s ease,background .22s ease,border-color .22s ease,box-shadow .24s ease,transform .24s cubic-bezier(.2,.8,.2,1)}
|
|
1468
|
+
:is([class*="_tab"]):not([class*="_tabs"]):not([class*="_tabStrip"]):not([class*="_tabLayout"]):hover{color:var(--dsw-alias-label-primary);background:color-mix(in srgb,var(--ds-brand) 16%,transparent);border-color:color-mix(in srgb,var(--ds-brand) 42%,transparent);box-shadow:0 4px 12px color-mix(in srgb,var(--ds-brand) 22%,transparent);transform:translateY(-1px)}
|
|
1469
|
+
:is([class*="_tab"]):not([class*="_tabs"]):not([class*="_tabStrip"]):not([class*="_tabLayout"]):active{transform:translateY(0) scale(.96)}
|
|
1470
|
+
:is([class*="_tab"]):not([class*="_tabs"]):not([class*="_tabStrip"]):not([class*="_tabLayout"])::after{display:none!important}
|
|
1471
|
+
:is([class*="_tabActive"]){background:linear-gradient(135deg,var(--ds-brand),var(--ds-brand-mid))!important;color:#fff!important;border-color:transparent!important;box-shadow:0 3px 10px color-mix(in srgb,var(--ds-brand) 26%,transparent);animation:dshome-tab-pop .34s cubic-bezier(.2,.8,.2,1)}
|
|
1472
|
+
:is([class*="_tabActive"]):hover{color:#fff!important}
|
|
1473
|
+
body[data-dshome-dark] :is([class*="_tab"]):not([class*="_tabs"]):not([class*="_tabStrip"]):not([class*="_tabLayout"]){border-color:color-mix(in srgb,var(--ds-brand) 32%,transparent);background:color-mix(in srgb,var(--ds-brand) 14%,transparent)}
|
|
1474
|
+
body[data-dshome-dark] :is([class*="_tab"]):not([class*="_tabs"]):not([class*="_tabStrip"]):not([class*="_tabLayout"]):hover{background:color-mix(in srgb,var(--ds-brand) 24%,transparent);box-shadow:0 4px 14px color-mix(in srgb,var(--ds-brand) 34%,transparent)}
|
|
1475
|
+
body[data-dshome-dark] :is([class*="_tabActive"]){box-shadow:0 3px 12px color-mix(in srgb,var(--ds-brand) 40%,transparent)}
|
|
1410
1476
|
@keyframes dshome-tab-pop{0%{transform:translateY(-1px) scale(.88)}60%{transform:translateY(-1px) scale(1.05)}to{transform:none}}
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
/*
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
.wSkVaW_header .CAgGvG_main,.wSkVaW_header .CAgGvG_chevron{transition:background .22s ease,color .22s ease}
|
|
1427
|
-
.wSkVaW_header .CAgGvG_split:hover .CAgGvG_main,.wSkVaW_header .CAgGvG_split:hover .CAgGvG_chevron{background:color-mix(in srgb,var(--ds-brand) 12%,transparent);color:var(--ds-brand-deep)}
|
|
1428
|
-
.wSkVaW_header .CAgGvG_chevron svg{transition:transform .3s cubic-bezier(.2,.8,.2,1)}
|
|
1429
|
-
.wSkVaW_header .CAgGvG_chevron[aria-expanded=true] svg{transform:rotate(180deg)}
|
|
1430
|
-
body[data-dshome-dark] .wSkVaW_header .CAgGvG_split:hover{border-color:color-mix(in srgb,var(--ds-brand) 52%,transparent);background:color-mix(in srgb,var(--ds-brand) 16%,transparent);box-shadow:0 4px 16px color-mix(in srgb,var(--ds-brand) 30%,transparent)}
|
|
1431
|
-
body[data-dshome-dark] .wSkVaW_header .CAgGvG_split:hover .CAgGvG_main,body[data-dshome-dark] .wSkVaW_header .CAgGvG_split:hover .CAgGvG_chevron{background:color-mix(in srgb,var(--ds-brand) 24%,transparent);color:#fff}
|
|
1432
|
-
` },
|
|
1433
|
-
// Session-log button (hover expand). The product renamed it to
|
|
1434
|
-
// `nL4_yW_moreButton` on the current build (B2, 2026-09-16), so anchor and
|
|
1435
|
-
// rules both key on the suffix, the way `[class*="_fade" i]` already does.
|
|
1436
|
-
{ anchor: '[class*="_moreButton"]', css: `
|
|
1477
|
+
/* 路径标签(会话标题)胶囊 + 品牌标签胶囊 */
|
|
1478
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_crumb"]){border-radius:999px}
|
|
1479
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_label"]){background:color-mix(in srgb,var(--ds-brand) 10%,transparent)!important;border:1px solid color-mix(in srgb,var(--ds-brand) 22%,transparent);border-radius:999px!important;padding:0 10px!important;height:28px;line-height:26px;color:var(--ds-brand)!important}
|
|
1480
|
+
body[data-dshome-dark] :has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_label"]){color:var(--ds-brand-light)!important}
|
|
1481
|
+
/* 打开方式的分裂按钮:悬停抬升 + 品牌着色,菜单打开时箭头翻过来 */
|
|
1482
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_split"]){transition:transform .28s cubic-bezier(.2,.8,.2,1),box-shadow .28s ease,border-color .22s ease,background .22s ease}
|
|
1483
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_split"]):hover{transform:translateY(-1px);border-color:color-mix(in srgb,var(--ds-brand) 40%,transparent);background:color-mix(in srgb,var(--ds-brand) 8%,transparent);box-shadow:0 4px 14px color-mix(in srgb,var(--ds-brand) 22%,transparent)}
|
|
1484
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_split"]):active{transform:translateY(0) scale(.97)}
|
|
1485
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_chevron"]) svg{transition:transform .3s cubic-bezier(.2,.8,.2,1)}
|
|
1486
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_chevron"])[aria-expanded=true] svg{transform:rotate(180deg)}
|
|
1487
|
+
/* 语言开关:悬停标题栏时 中 → 中文 展开(原样的行为) */
|
|
1488
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover .dshome-locale .dshome-short{max-width:0;opacity:0;transform:translateX(6px)}
|
|
1489
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover .dshome-locale .dshome-full{max-width:3em;opacity:1;transform:none}
|
|
1490
|
+
/* 真圆配对:产品的全局 superellipse(1.5) 会把胶囊半径压成方圆 */
|
|
1491
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]) :is([class*="_label"],[class*="_seat"],[class*="_split"],[class*="_crumb"],[class*="_moreButton"],[data-sidebar-right-expand]),.dshome-locale,[class*="_tabs"]{corner-shape:round}
|
|
1437
1492
|
:is([class*="_moreButton"]){min-width:0!important;width:28px;height:28px;padding:0!important;border-radius:50%!important;border:none!important;background:color-mix(in srgb,var(--ds-brand) 8%,transparent)!important;color:var(--ds-brand)!important;overflow:hidden;transition:width .2s ease,padding .2s ease,border-radius .2s ease}
|
|
1438
1493
|
:is([class*="_moreButton"]) span{max-width:0;opacity:0;overflow:hidden;transition:max-width .2s ease,opacity .2s ease}
|
|
1439
|
-
|
|
1440
|
-
|
|
1494
|
+
/* 原来这里写的是 :has(_moreButton) —— 那等于"只要存在就展开",形态与原来不符。
|
|
1495
|
+
原设计是**悬停标题栏才展开**成宽胶囊。 */
|
|
1496
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover :is([class*="_moreButton"]){width:auto;padding:0 12px!important;border-radius:18px!important}
|
|
1497
|
+
:has(> [class*="_headerLeading"]):has([class*="_headerActions"]):hover :is([class*="_moreButton"]) span{max-width:90px;opacity:1}
|
|
1441
1498
|
body[data-dshome-dark] :is([class*="_moreButton"]){color:var(--ds-brand-light)!important}
|
|
1499
|
+
:is([class*="_newSession"]){border:none!important;background:linear-gradient(135deg,var(--ds-brand) 0%,var(--ds-brand-mid) 100%)!important;color:#fff!important;border-radius:999px!important;box-shadow:0 4px 14px color-mix(in srgb,var(--ds-brand) 35%,transparent);transition:transform .15s ease,box-shadow .15s ease,background .15s ease}
|
|
1500
|
+
:is([class*="_newSession"]):hover{background:linear-gradient(135deg,color-mix(in srgb,var(--ds-brand) 72%,#fff) 0%,var(--ds-brand) 100%)!important;box-shadow:0 6px 18px color-mix(in srgb,var(--ds-brand) 45%,transparent);transform:translateY(-1px)}
|
|
1442
1501
|
` },
|
|
1443
|
-
//
|
|
1444
|
-
//
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
.uV2eYG_card::after{content:"";position:absolute;inset:0;z-index:-1;border-radius:22px;-webkit-backdrop-filter:blur(28px);backdrop-filter:blur(28px);pointer-events:none}
|
|
1449
|
-
.uV2eYG_card::before{content:"";position:absolute;inset:-1px;border-radius:inherit;padding:1px;background:conic-gradient(from var(--dsh-border-angle),color-mix(in srgb,var(--ds-brand) 0%,transparent),color-mix(in srgb,var(--ds-brand) 45%,transparent),color-mix(in srgb,var(--ds-brand-light) 20%,transparent),color-mix(in srgb,var(--ds-brand) 45%,transparent),color-mix(in srgb,var(--ds-brand) 0%,transparent));-webkit-mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);-webkit-mask-composite:xor;mask:linear-gradient(#000 0 0) content-box,linear-gradient(#000 0 0);mask-composite:exclude;pointer-events:none;animation:dsh-border-spin 6s linear infinite;z-index:0}
|
|
1450
|
-
body[data-dshome-dark] .uV2eYG_card::before{background:conic-gradient(from var(--dsh-border-angle),rgba(255,255,255,0),color-mix(in srgb,var(--ds-brand) 55%,transparent),color-mix(in srgb,var(--ds-brand-light) 25%,transparent),color-mix(in srgb,var(--ds-brand) 55%,transparent),rgba(255,255,255,0))}
|
|
1451
|
-
.uV2eYG_card>*{position:relative;z-index:1}
|
|
1452
|
-
.uV2eYG_primary{background:linear-gradient(135deg,var(--ds-brand) 0%,var(--ds-brand-mid) 100%)!important;border:none!important;color:#fff!important;box-shadow:0 4px 14px color-mix(in srgb,var(--ds-brand) 35%,transparent);transition:box-shadow .15s ease,transform .15s ease}
|
|
1453
|
-
.uV2eYG_primary:hover{box-shadow:0 6px 18px color-mix(in srgb,var(--ds-brand) 45%,transparent)}
|
|
1454
|
-
/* 2.0.80: the hover no longer lifts the button by 1px. That translateY(-1px)
|
|
1455
|
-
was ours (it came with the primary-button styling, not from the product), and
|
|
1456
|
-
it was the last geometry change this theme made to the send/stop button under
|
|
1457
|
-
the cursor -- a 1px surface motion is enough to hand the hover to a neighbour
|
|
1458
|
-
or to the column's width handle, which is the same class of loop the composer
|
|
1459
|
-
tilt caused (2.0.79). Hover now changes colour and shadow only. The product's
|
|
1460
|
-
own hover on this button changes nothing but its background, so nothing else
|
|
1461
|
-
moves it. */
|
|
1462
|
-
` },
|
|
1463
|
-
// The conversation scroller must never scroll sideways (2.0.78).
|
|
1464
|
-
//
|
|
1465
|
-
// Measured on the live page while the user hovered the composer's
|
|
1466
|
-
// send button and the small circle: the product's own hover affordance
|
|
1467
|
-
// on a message lays its row out wider than the column (scrollWidth 917
|
|
1468
|
-
// vs clientWidth 891, overflowing element `span._bubble_...`), which
|
|
1469
|
-
// summons an 8 px horizontal scrollbar. That scrollbar reserves 8 px of
|
|
1470
|
-
// content height, so the scroll body's content box goes 842 -> 834 and
|
|
1471
|
-
// the composer pinned below it shifts up 8 px (card top 699 -> 691);
|
|
1472
|
-
// the pointer then sits over a different element, the affordance
|
|
1473
|
-
// closes, the scrollbar leaves, the composer drops back -- a ~5 Hz
|
|
1474
|
-
// bistable loop reported as "the two buttons flicker and a slider
|
|
1475
|
-
// flashes at the bottom". The overflow is upstream's, but the theme can
|
|
1476
|
-
// decline to scroll sideways at all: `clip` reserves no scrollbar and
|
|
1477
|
-
// keeps each code block's own inner scroller working. The trade is that
|
|
1478
|
-
// whatever sticks out past the column edge is clipped instead of
|
|
1479
|
-
// scrollable, which in a chat column is the lesser evil -- and this is
|
|
1480
|
-
// the guarded-block pattern, so it disappears with the anchor.
|
|
1481
|
-
//
|
|
1482
|
-
// 2.0.82 widens the net to the column's other scrollers. The mechanism
|
|
1483
|
-
// needs only ONE `overflow-y: auto` container (the other axis computes
|
|
1484
|
-
// to `auto`, so it can grow a horizontal scrollbar) whose child grows on
|
|
1485
|
-
// hover; the first report named the message list, but the same 8px step
|
|
1486
|
-
// under the send button can come from the composer's own seat or the
|
|
1487
|
-
// transcript's view area, which that first fix did not cover. Popups
|
|
1488
|
-
// portalled to <body> are unaffected; anything rendered inline inside
|
|
1489
|
-
// these containers is clipped, exactly as it already was for the list.
|
|
1490
|
-
{ anchor: ".wSkVaW_scrollBody", css: `
|
|
1491
|
-
[class*="scrollBody"],[class*="composerSeat"],[class*="viewArea"]{overflow-x:clip}
|
|
1492
|
-
` },
|
|
1493
|
-
// Empty-state headline + preview badge. The product renamed
|
|
1494
|
-
// `pXSMma_headlineText` to `pXSMma_headline` (B2, 2026-09-16), which
|
|
1495
|
-
// silently killed this block and left the headline at 26px/500 with no
|
|
1496
|
-
// gradient. Keyed on the suffix now, along with the two brand marks that
|
|
1497
|
-
// live in the same family.
|
|
1498
|
-
{ anchor: '[class*="_headline"]', css: `
|
|
1499
|
-
:is([class*="_headline"]){font-size:34px;line-height:42px;font-weight:700;letter-spacing:-.01em;background:linear-gradient(120deg,#152443 0%,var(--ds-brand-deep) 55%,var(--ds-brand) 100%);-webkit-background-clip:text;background-clip:text;color:transparent}
|
|
1500
|
-
body[data-dshome-dark] :is([class*="_headline"]){background:linear-gradient(120deg,#fff 0%,var(--ds-brand-light) 60%,var(--ds-brand) 100%);-webkit-background-clip:text;background-clip:text}
|
|
1501
|
-
:is([class*="_previewBadge"]){border-radius:999px!important;background:color-mix(in srgb,var(--ds-brand) 10%,transparent)!important;border:1px solid color-mix(in srgb,var(--ds-brand) 25%,transparent)!important;color:var(--ds-brand)!important}
|
|
1502
|
-
:is([class*="_fish"]){color:var(--ds-brand)!important}
|
|
1503
|
-
` },
|
|
1504
|
-
// Chat bubbles — liquid glass panes.
|
|
1505
|
-
{ anchor: ".wSkVaW_scrollBody", css: `
|
|
1506
|
-
:is(${USER_BUBBLE}){background:rgba(255,255,255,.6)!important;-webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);border:1px solid rgba(0,0,0,.05);box-shadow:0 4px 16px rgba(13,30,60,.06)}
|
|
1507
|
-
body[data-dshome-dark] :is(${USER_BUBBLE}){background:rgba(21,42,86,.65)!important;border:1px solid rgba(255,255,255,.1);box-shadow:0 4px 16px rgba(0,0,0,.25)}
|
|
1508
|
-
` },
|
|
1509
|
-
// The line under the composer: round/step/token stats ([data-dsh-stats]) and
|
|
1510
|
-
// the whole input bar ([data-dsh-inputbar]) -- both product markup that has
|
|
1511
|
-
// never been renamed.
|
|
1512
|
-
//
|
|
1513
|
-
// Two things happen here.
|
|
1514
|
-
//
|
|
1515
|
-
// 1. The DOCKED BOTTOM BAR, borrowed from dsh-theme-mineradio's fused state
|
|
1516
|
-
// (MIT, @dhicoc): once the stats line exists, the WHOLE input bar becomes
|
|
1517
|
-
// one glass slab -- a single border, radius, fill and shadow on the
|
|
1518
|
-
// wrapper, with the backdrop blur on its ::before -- and the card and the
|
|
1519
|
-
// stats turn transparent inside it, leaving one hairline at the seam. The
|
|
1520
|
-
// two surfaces can then never drift apart tonally, and the composer reads
|
|
1521
|
-
// as one designed bar instead of a card with loose text under it.
|
|
1522
|
-
// `:has()` is what makes this safe to attempt at all: when the product
|
|
1523
|
-
// does not render a stats line, none of it applies and the card keeps its
|
|
1524
|
-
// own glass.
|
|
1525
|
-
// Two details are load-bearing and inherited from mineradio's notes:
|
|
1526
|
-
// z-index (not isolation) on the wrapper -- isolation is also a backdrop
|
|
1527
|
-
// root and would clip popup frost to the bar -- and `backdrop-filter:
|
|
1528
|
-
// none` on the inner surfaces, whose own blur would otherwise double up
|
|
1529
|
-
// with the slab's.
|
|
1502
|
+
// 表格的滚动容器:产品给它的是 999px 胶囊 + `overflow:auto` —— 胶囊半径在
|
|
1503
|
+
// 一个 824×226 的盒子上每边要吃约 113px,而 overflow:auto 就在那条弧线上裁,
|
|
1504
|
+
// 于是宽表格的左右两端被**自己的容器**裁没(不是被浮层盖住)。
|
|
1505
|
+
// 2026-10-06 用户实测:`<table>` 本身 radius=0px / overflow=visible,
|
|
1506
|
+
// 而它的父层 `_tableScroll_* _tableFill_*` 是 radius=999px / overflow=auto。
|
|
1530
1507
|
//
|
|
1531
|
-
//
|
|
1532
|
-
//
|
|
1533
|
-
//
|
|
1534
|
-
//
|
|
1535
|
-
//
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1508
|
+
// 改成 8px 小圆角:边缘不再被吃,形态仍与整页的圆角语言一致。
|
|
1509
|
+
// 这一条是**我们主动覆盖产品的设计**,不在《逐项决定》11 条之内,由用户点名选择;
|
|
1510
|
+
// 记在这里是因为它属于"产品换做法时不会跟着变"的那一类,将来要能一眼认出。
|
|
1511
|
+
// 只为矩形感更强可以把 8px 调成 0;圆角与裁切是同一个属性的两面,
|
|
1512
|
+
// 想既留胶囊又不裁在 CSS 里做不到(除非给内容加够内边距)。
|
|
1513
|
+
{ anchor: '[class*="_tableScroll"], [class*="_scrollBody"]', css: `
|
|
1514
|
+
:is([class*="_tableScroll"]){border-radius:8px!important}
|
|
1515
|
+
` }, // 让流动背景透出来。产品给每一块整屏面板都铺了不透明的
|
|
1516
|
+
// `background:var(--dsw-alias-bg-base)`,而我们的底纹(body::before,z-index -2)
|
|
1517
|
+
// 和流体画布(z-index -1)都在 body 背景之下、内容之上——所以只要上面任何一层
|
|
1518
|
+
// 面板是不透明的,流体就等于没画。2026-10-03 的窗口截图量到会话区恒为 #f6f7fb
|
|
1519
|
+
// (= 底纹末段的颜色),而主题正常工作时同一个点是 #a7dfc9(明显的流场色)。
|
|
1520
|
+
// 2.0.90 里这条是写死哈希的(.pI_x6G_frame/_sidebarCol/_detailsCol/_centerCol),
|
|
1521
|
+
// 语义化时整块丢了。这里按结构认:
|
|
1522
|
+
// · 应用外框 = 同时把 centerCol 与 sidebarCol 当**直接子元素**的那一层(产品的
|
|
1523
|
+
// 外框是 display:grid,分栏就是它的网格子项);
|
|
1524
|
+
// · 会话根 = div[class*="_root"][data-phase"](产品 JSX 里它是
|
|
1525
|
+
// `className: ConversationRoot.root, "data-phase": phase`,实测这个组合唯一)。
|
|
1526
|
+
// 面板本身不动布局,只把底色让出来:文字仍在内容层,玻璃由各自的面提供。
|
|
1527
|
+
{ anchor: '[class*="_centerCol"], [class*="_scrollBody"]', css: `
|
|
1528
|
+
:has(> [class*="_centerCol"]):has(> [class*="_sidebarCol"]){background:transparent!important}
|
|
1529
|
+
[class*="_centerCol"],[class*="_rightbarCol"],[class*="_sidebarCol"]{background:transparent!important}
|
|
1530
|
+
div[class*="_root"][data-phase]{background:transparent!important}
|
|
1531
|
+
[class*="_composerSeat"]{background:transparent!important}
|
|
1549
1532
|
` },
|
|
1550
|
-
// Trajectory view bar.
|
|
1551
|
-
{ anchor: ".fV0t5q_root", css: `
|
|
1552
|
-
.fV0t5q_root{background:rgba(255,255,255,.45)!important;border-bottom:1px solid rgba(0,0,0,.06)!important}
|
|
1553
|
-
body[data-dshome-dark] .fV0t5q_root{background:rgba(13,30,60,.45)!important;border-bottom-color:rgba(255,255,255,.08)!important}
|
|
1554
|
-
` },
|
|
1555
|
-
// Timeline root/plot/span.
|
|
1556
|
-
{ anchor: "._1p9O6q_root", css: `
|
|
1557
|
-
._1p9O6q_root{background:rgba(255,255,255,.3)!important;border-bottom:1px solid rgba(0,0,0,.05)!important}
|
|
1558
|
-
._1p9O6q_plot{background:transparent!important}
|
|
1559
|
-
body[data-dshome-dark] ._1p9O6q_root{background:rgba(13,30,60,.3)!important}
|
|
1560
|
-
._1p9O6q_span[data-timeline-span=assistant]{background:var(--ds-brand)!important}
|
|
1561
|
-
._1p9O6q_span[data-timeline-span=user]{background:var(--ds-brand-light)!important}
|
|
1562
|
-
` },
|
|
1563
|
-
// Progress panel entrance.
|
|
1564
|
-
{ anchor: ".Nqubda_panel", css: `
|
|
1565
|
-
.Nqubda_panel{animation:dshome-pop-in .25s cubic-bezier(.2,.8,.2,1)}
|
|
1566
|
-
` },
|
|
1567
|
-
// Settings overlay / modal — near-opaque glass with specular edge.
|
|
1568
|
-
{ anchor: ".VOzbGW_panel", css: `
|
|
1569
|
-
.VOzbGW_mask{animation:dshome-fade-in .2s ease}
|
|
1570
|
-
.VOzbGW_panel{background:rgba(255,255,255,.97)!important;animation:dshome-modal-in .4s cubic-bezier(.2,.8,.2,1)!important}
|
|
1571
|
-
body[data-dshome-dark] .VOzbGW_panel{background:rgba(13,30,60,.96)!important}
|
|
1572
|
-
.VOzbGW_overlay{position:fixed!important;top:0!important;left:0!important;width:100vw!important;height:100vh!important;inset:0!important}
|
|
1573
|
-
` }
|
|
1574
1533
|
];
|
|
1575
1534
|
|
|
1576
1535
|
// LIQUID GLASS — the "wet glass" recipe, imitated from
|
|
@@ -1595,8 +1554,7 @@ body[data-dshome-dark] .VOzbGW_panel{background:rgba(13,30,60,.96)!important}
|
|
|
1595
1554
|
// `data-dshome-dispersion` attribute that startGlassDispersion() stamps on
|
|
1596
1555
|
// <html> only where it really mounted — every other browser silently keeps
|
|
1597
1556
|
// the plain chain.
|
|
1598
|
-
const GLASS_CSS =
|
|
1599
|
-
:root{
|
|
1557
|
+
const GLASS_CSS = `:root{
|
|
1600
1558
|
--dshome-glass-blur:16px;
|
|
1601
1559
|
--dshome-glass-saturate:1.42;
|
|
1602
1560
|
--dshome-glass-brightness:1;
|
|
@@ -1617,14 +1575,13 @@ body[data-dshome-dark]{
|
|
|
1617
1575
|
--dshome-glass-ring:rgba(255,255,255,.18);
|
|
1618
1576
|
--dshome-glass-shadow:0 14px 44px rgba(0,0,0,.45);
|
|
1619
1577
|
}
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
html
|
|
1627
|
-
html .dshome-file-menu{
|
|
1578
|
+
/* 用户气泡的基础玻璃。这一条是补回来的:2.0.90 里它和侧栏、输入卡挤在同一条
|
|
1579
|
+
多面合并规则里(侧栏外壳 ::before、输入卡 ::after、用户气泡三者共用一份
|
|
1580
|
+
填充与模糊)。语义化时侧栏和输入卡那两支搬进了 PATCH 补丁,
|
|
1581
|
+
气泡这支没人接——于是气泡只剩下面两条 :hover,静置状态一点玻璃都没有。
|
|
1582
|
+
这里只补气泡:侧栏由侧栏补丁画、输入卡由输入卡补丁画,都不要再画第二遍。
|
|
1583
|
+
属性与 2.0.90 逐字一致(含 !important 的位置),免得白磨砂档位换了个样子。 */
|
|
1584
|
+
html :is(${USER_BUBBLE}){
|
|
1628
1585
|
background-image:
|
|
1629
1586
|
radial-gradient(340px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .30)), transparent 62%),
|
|
1630
1587
|
linear-gradient(180deg, rgba(255,255,255,var(--dshome-glass-sheen)), rgba(255,255,255,var(--dshome-glass-sheen-mid)) 38%, rgba(255,255,255,.01))!important;
|
|
@@ -1633,40 +1590,30 @@ html .dshome-file-menu{
|
|
|
1633
1590
|
box-shadow:inset 0 1px 0 var(--dshome-glass-edge),inset 0 -1px 0 var(--dshome-glass-edge-low),inset 0 0 0 .5px var(--dshome-glass-ring),var(--dshome-glass-shadow);
|
|
1634
1591
|
transition:background-color .18s ease,box-shadow .18s ease;
|
|
1635
1592
|
}
|
|
1593
|
+
/* 没有 backdrop-filter 的引擎:气泡退回不透明底,不做半透明假玻璃。 */
|
|
1594
|
+
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
|
|
1595
|
+
html :is(${USER_BUBBLE}){
|
|
1596
|
+
background-color:rgba(255,255,255,.86)!important;
|
|
1597
|
+
}
|
|
1598
|
+
body[data-dshome-dark] :is(${USER_BUBBLE}){
|
|
1599
|
+
background-color:rgba(13,30,60,.92)!important;
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1636
1602
|
/* The specular layer is multiplied by --dshome-glass-spec, so it has to be
|
|
1637
1603
|
switched on: while the cursor is over a spot surface the highlight follows
|
|
1638
1604
|
--dshome-spec-x/y (written per element by startSpecularParallax). */
|
|
1639
1605
|
html [data-dshome-spot]:hover{--dshome-glass-spec:1;}
|
|
1640
|
-
html
|
|
1641
|
-
html :is(${USER_BUBBLE}
|
|
1606
|
+
html ${COMPOSER_PLATE}:hover{--dshome-glass-spec:1;--dshome-glass-sheen:.12;}
|
|
1607
|
+
html :is(${USER_BUBBLE}
|
|
1608
|
+
):hover,html .dshome-sa:hover,html .dshome-file-menu:hover{
|
|
1642
1609
|
background-color:rgb(255 255 255 / calc(var(--dshome-glass-sheen) + .16))!important;
|
|
1643
1610
|
}
|
|
1644
|
-
body[data-dshome-dark] :is(${USER_BUBBLE}
|
|
1611
|
+
body[data-dshome-dark] :is(${USER_BUBBLE}
|
|
1612
|
+
):hover,
|
|
1645
1613
|
body[data-dshome-dark] .dshome-sa:hover,
|
|
1646
1614
|
body[data-dshome-dark] .dshome-file-menu:hover{
|
|
1647
1615
|
background-color:rgb(255 255 255 / calc(var(--dshome-glass-sheen) + .06))!important;
|
|
1648
|
-
}
|
|
1649
|
-
html[data-dshome-dispersion] .hHd-Xa_root::before,
|
|
1650
|
-
html[data-dshome-dispersion] :is(${USER_BUBBLE}),
|
|
1651
|
-
html[data-dshome-dispersion] .VOzbGW_panel{
|
|
1652
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate))!important;
|
|
1653
|
-
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-glass-blur)) saturate(var(--dshome-glass-saturate))!important;
|
|
1654
|
-
}
|
|
1655
|
-
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
|
|
1656
|
-
html .hHd-Xa_root::before,html .uV2eYG_card::after,html :is(${USER_BUBBLE}),html [data-composer-card],html .VOzbGW_panel,html .dshome-sa,html .dshome-file-menu{
|
|
1657
|
-
background-color:rgba(255,255,255,.86)!important;
|
|
1658
|
-
}
|
|
1659
|
-
body[data-dshome-dark] .hHd-Xa_root::before,
|
|
1660
|
-
body[data-dshome-dark] .uV2eYG_card::after,
|
|
1661
|
-
body[data-dshome-dark] :is(${USER_BUBBLE}),
|
|
1662
|
-
body[data-dshome-dark] [data-composer-card],
|
|
1663
|
-
body[data-dshome-dark] .VOzbGW_panel,
|
|
1664
|
-
body[data-dshome-dark] .dshome-sa,
|
|
1665
|
-
body[data-dshome-dark] .dshome-file-menu{
|
|
1666
|
-
background-color:rgba(13,30,60,.92)!important;
|
|
1667
|
-
}
|
|
1668
|
-
}
|
|
1669
|
-
`;
|
|
1616
|
+
}`;
|
|
1670
1617
|
|
|
1671
1618
|
/** Append a style tag; returns a disposer that removes it. */
|
|
1672
1619
|
function injectStyle(css, label) {
|
|
@@ -1712,8 +1659,7 @@ html[data-dshome-dispersion] .VOzbGW_panel{
|
|
|
1712
1659
|
// (USER_BUBBLE itself is declared above PATCH_BLOCKS: this sheet and the
|
|
1713
1660
|
// patch blocks both interpolate it, so it has to exist before either is
|
|
1714
1661
|
// built.)
|
|
1715
|
-
const LIQUID_CSS =
|
|
1716
|
-
@property --dshome-liquid-sweep{syntax:"<angle>";inherits:false;initial-value:0deg}
|
|
1662
|
+
const LIQUID_CSS = `@property --dshome-liquid-sweep{syntax:"<angle>";inherits:false;initial-value:0deg}
|
|
1717
1663
|
@keyframes dshome-liquid-sweep{from{--dshome-liquid-sweep:0deg}to{--dshome-liquid-sweep:360deg}}
|
|
1718
1664
|
html[data-dshome-glass="liquid"]{
|
|
1719
1665
|
--dshome-liquid-blur:11px;
|
|
@@ -1766,19 +1712,16 @@ body[data-dshome-dark]{
|
|
|
1766
1712
|
--dshome-liquid-sweep-lo:rgba(255,255,255,.16);
|
|
1767
1713
|
--dshome-liquid-shadow:0 20px 60px rgba(0,0,0,.5);
|
|
1768
1714
|
}
|
|
1769
|
-
/*
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before,
|
|
1780
|
-
html[data-dshome-glass="liquid"] body [data-composer-card],
|
|
1781
|
-
html[data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1715
|
+
/* 液态玻璃的主规则——填充、光标高光、顶部光泽、背板模糊链、内外描边与投影。
|
|
1716
|
+
2.1.0 语义化时这条规则的选择器头整段被删掉了,只剩一个孤零零的右括号加左花括号,
|
|
1717
|
+
浏览器把整条丢掉,于是四个面的液态玻璃一起消失。
|
|
1718
|
+
这里按语义钩子重建三个面:侧栏外壳(::before,产品自己占了这个伪元素)、
|
|
1719
|
+
输入卡玻璃板([data-composer-stats] 的上层容器)、用户气泡。
|
|
1720
|
+
标题栏那一支等标题栏恢复后由同一条规则接管(见 PATCH 的标题栏块)。
|
|
1721
|
+
选择器带一层 body:PATCH 补丁是等锚点出现才注入的,比这张表晚,
|
|
1722
|
+
同权重时源序会把面交回旧的填充,多一个类型选择器就断开这个平手。 */
|
|
1723
|
+
html[data-dshome-glass="liquid"] body :is(${SIDEBAR})::before,
|
|
1724
|
+
html[data-dshome-glass="liquid"] body ${COMPOSER_PLATE},
|
|
1782
1725
|
html[data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1783
1726
|
background-color:var(--dshome-liquid-fill)!important;
|
|
1784
1727
|
background-image:
|
|
@@ -1788,17 +1731,6 @@ html[data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
|
1788
1731
|
backdrop-filter:blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1789
1732
|
box-shadow:inset 0 1px 0 var(--dshome-liquid-edge),inset 0 -1px 0 var(--dshome-liquid-edge-low),inset 0 0 0 .5px var(--dshome-liquid-ring),var(--dshome-liquid-shadow)!important;
|
|
1790
1733
|
}
|
|
1791
|
-
/* The composer is the one surface the old recipe paints TWICE: the patch block
|
|
1792
|
-
blurs .uV2eYG_card::after (and the frosted list paints it too), and the card
|
|
1793
|
-
itself is in both lists, so the backdrop was blurred twice over — which is what
|
|
1794
|
-
turned the pane into a smear. Under liquid the card element IS the glass, so
|
|
1795
|
-
the redundant layer is switched off rather than re-tuned. */
|
|
1796
|
-
html[data-dshome-glass="liquid"] [data-composer-card]::after{
|
|
1797
|
-
-webkit-backdrop-filter:none!important;
|
|
1798
|
-
backdrop-filter:none!important;
|
|
1799
|
-
background-image:none!important;
|
|
1800
|
-
box-shadow:none!important;
|
|
1801
|
-
}
|
|
1802
1734
|
/* The frosted recipe gives the composer a brand-tinted conic ring, painted by
|
|
1803
1735
|
the patch block at inset:-1px and spun by dsh-border-spin. Under liquid the
|
|
1804
1736
|
user asked for it to go: it sits one pixel OUTSIDE the card's own 1.5px white
|
|
@@ -1818,25 +1750,12 @@ html[data-dshome-glass="liquid"] body [data-composer-card]::before{
|
|
|
1818
1750
|
html[data-dshome-glass="liquid"] body [data-composer-card]{
|
|
1819
1751
|
border-color:transparent!important;
|
|
1820
1752
|
}
|
|
1821
|
-
/* The sidebar pane carries its own fill and its own shape: rounded only on the
|
|
1822
|
-
edge that faces the conversation, so it reads as a panel clipped to the
|
|
1823
|
-
window rather than as a full-bleed column. The hairline ring in the shared
|
|
1824
|
-
box-shadow above follows the radius. */
|
|
1825
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
1826
|
-
background-color:var(--dshome-liquid-fill-sidebar)!important;
|
|
1827
|
-
border-radius:0 22px 22px 0;
|
|
1828
|
-
/* The veil rides on top of the two layers the shared rule sets, so the
|
|
1829
|
-
sidebar keeps its cursor-follow highlight and its sheen underneath. */
|
|
1830
|
-
background-image:
|
|
1831
|
-
linear-gradient(var(--dshome-liquid-veil-sidebar),var(--dshome-liquid-veil-sidebar)),
|
|
1832
|
-
radial-gradient(360px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .24)), transparent 60%),
|
|
1833
|
-
linear-gradient(180deg, rgba(255,255,255,var(--dshome-liquid-sheen)), rgba(255,255,255,.015) 34%, rgba(255,255,255,0))!important;
|
|
1834
|
-
}
|
|
1835
1753
|
/* The message bubble is the one surface that is not already positioned. The
|
|
1836
1754
|
tooltip is not in this list any more -- that was B5: making a
|
|
1837
1755
|
position:fixed tooltip relative turned it into an in-flow 78px block that
|
|
1838
1756
|
shoved the send button out from under the cursor. See USER_BUBBLE. */
|
|
1839
|
-
html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}
|
|
1757
|
+
html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}
|
|
1758
|
+
){
|
|
1840
1759
|
position:relative;
|
|
1841
1760
|
transition:background-color .18s ease,box-shadow .18s ease;
|
|
1842
1761
|
}
|
|
@@ -1849,30 +1768,51 @@ html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}){
|
|
|
1849
1768
|
Chromium's backdrop sampling is unreliable on a will-changed element, and the
|
|
1850
1769
|
compositor already promotes a transformed element while it is actually being
|
|
1851
1770
|
transformed. The hint cost the effect it was meant to smooth. */
|
|
1852
|
-
html[data-dshome-glass="liquid"]
|
|
1771
|
+
html[data-dshome-glass="liquid"] ${COMPOSER_PLATE}{
|
|
1853
1772
|
transition:transform .24s cubic-bezier(.2,.8,.2,1),background-color .18s ease,box-shadow .18s ease!important;
|
|
1854
1773
|
}
|
|
1855
|
-
html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}
|
|
1774
|
+
html[data-dshome-glass="liquid"] :is(${USER_BUBBLE}
|
|
1775
|
+
):hover{
|
|
1856
1776
|
background-color:rgb(255 255 255 / .18)!important;
|
|
1857
1777
|
}
|
|
1858
1778
|
/* Liquid's dark hover used to sit OUTSIDE the gate, so it also repainted the
|
|
1859
1779
|
frosted recipe's bubbles with liquid's values. Gated like its siblings. */
|
|
1860
|
-
html[data-dshome-glass="liquid"] body[data-dshome-dark] :is(${USER_BUBBLE}
|
|
1780
|
+
html[data-dshome-glass="liquid"] body[data-dshome-dark] :is(${USER_BUBBLE}
|
|
1781
|
+
):hover{
|
|
1861
1782
|
background-color:rgb(255 255 255 / .10)!important;
|
|
1862
1783
|
}
|
|
1863
|
-
|
|
1784
|
+
/* 用户气泡的边缘折射。和主规则一样,这条也只剩半截,头被删了。 */
|
|
1785
|
+
html[data-dshome-dispersion][data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1786
|
+
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1787
|
+
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1788
|
+
}
|
|
1789
|
+
/* 侧栏自己那一份:更重的填充(它是屏上最大的一块面,用气泡那种"几乎看不见"
|
|
1790
|
+
的填充会被流体洗掉)、更重的模糊(它是与会话区的分界,共用 11px 时身后的
|
|
1791
|
+
流场太清楚,面板反而和正文粘成一片)、以及一层额外面纱把它从会话区上抬起来。
|
|
1792
|
+
2.1.0 里这一整块(含下面 ::after 的走光边环)都不存在。 */
|
|
1793
|
+
html[data-dshome-glass="liquid"] body :is(${SIDEBAR})::before{
|
|
1794
|
+
background-color:var(--dshome-liquid-fill-sidebar)!important;
|
|
1795
|
+
border-radius:0 22px 22px 0;
|
|
1796
|
+
background-image:
|
|
1797
|
+
linear-gradient(var(--dshome-liquid-veil-sidebar),var(--dshome-liquid-veil-sidebar)),
|
|
1798
|
+
radial-gradient(360px circle at calc(50% + var(--dshome-spec-x,0) * 50%) calc(50% + var(--dshome-spec-y,0) * 50%), rgb(255 255 255 / calc(var(--dshome-glass-spec) * .24)), transparent 60%),
|
|
1799
|
+
linear-gradient(180deg, rgba(255,255,255,var(--dshome-liquid-sheen)), rgba(255,255,255,.015) 34%, rgba(255,255,255,0))!important;
|
|
1800
|
+
-webkit-backdrop-filter:blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1801
|
+
backdrop-filter:blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1802
|
+
}
|
|
1803
|
+
html[data-dshome-dispersion][data-dshome-glass="liquid"] body :is(${SIDEBAR})::before{
|
|
1804
|
+
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1805
|
+
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1806
|
+
}
|
|
1807
|
+
html[data-dshome-glass="liquid"] :is(${SIDEBAR})::after{
|
|
1864
1808
|
border-radius:0 22px 22px 0;
|
|
1865
1809
|
}
|
|
1866
|
-
/*
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
composer it doubled up with the card's own border. The bubbles and the
|
|
1873
|
-
composer keep liquid's other three marks — the translucent fill, the sheen
|
|
1874
|
-
and the refraction. */
|
|
1875
|
-
html[data-dshome-glass="liquid"] .hHd-Xa_root::after{
|
|
1810
|
+
/* 沿着玻璃边跑的那圈高光。mask-composite:exclude 把填充的 conic 渐变掏成一条
|
|
1811
|
+
1px 的边带;伪元素上的 padding:1px 定这条带的宽度,不动产品的盒模型。
|
|
1812
|
+
只画在侧栏:用户明确要求从输入卡和气泡上去掉(小胶囊上那条带读起来像一圈
|
|
1813
|
+
白描边,不像光在玻璃边上走),那两处保留液态的另外三个标记——半透明填充、
|
|
1814
|
+
光泽、折射。 */
|
|
1815
|
+
html[data-dshome-glass="liquid"] :is(${SIDEBAR})::after{
|
|
1876
1816
|
content:"";position:absolute;inset:0;z-index:2;pointer-events:none;border-radius:inherit;
|
|
1877
1817
|
padding:1px;
|
|
1878
1818
|
background:conic-gradient(from var(--dshome-liquid-sweep),
|
|
@@ -1885,54 +1825,29 @@ html[data-dshome-glass="liquid"] .hHd-Xa_root::after{
|
|
|
1885
1825
|
mask-composite:exclude;
|
|
1886
1826
|
animation:dshome-liquid-sweep 6.5s linear infinite;
|
|
1887
1827
|
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
off -- no dispersion at all
|
|
1893
|
-
Left and right are left as they fall: the band is there, but over a
|
|
1894
|
-
continuous background a horizontal displacement has nothing to show, which
|
|
1895
|
-
the user accepted. */
|
|
1896
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1897
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1898
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1899
|
-
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1828
|
+
@media (prefers-reduced-motion:reduce){
|
|
1829
|
+
html[data-dshome-glass="liquid"] :is(${SIDEBAR})::after{
|
|
1830
|
+
animation:none;
|
|
1831
|
+
}
|
|
1900
1832
|
}
|
|
1901
|
-
|
|
1833
|
+
/* 侧栏底部那条淡出渐变,末段用的是产品自己的侧栏底色 token(不透明),
|
|
1834
|
+
压在半透明玻璃上就对不上身后真实的颜色,于是在「设置」上方露出一条浅带。
|
|
1835
|
+
两档配方都去掉它,不限于液态——白磨砂的侧栏同样是半透明的。 */
|
|
1836
|
+
body :is(${SIDEBAR}) [class*="_fade" i]{
|
|
1837
|
+
background-image:none!important;
|
|
1838
|
+
}
|
|
1839
|
+
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="narrow"] body ${COMPOSER_PLATE}{
|
|
1902
1840
|
-webkit-backdrop-filter:url(#dshome-glass-dispersion-composer) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1903
1841
|
backdrop-filter:url(#dshome-glass-dispersion-composer) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1904
1842
|
}
|
|
1905
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="wide"] body
|
|
1843
|
+
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="wide"] body ${COMPOSER_PLATE}{
|
|
1906
1844
|
-webkit-backdrop-filter:url(#dshome-glass-dispersion-composer-wide) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1907
1845
|
backdrop-filter:url(#dshome-glass-dispersion-composer-wide) blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1908
1846
|
}
|
|
1909
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="off"] body
|
|
1847
|
+
html[data-dshome-dispersion][data-dshome-glass="liquid"][data-dshome-composer="off"] body ${COMPOSER_PLATE}{
|
|
1910
1848
|
-webkit-backdrop-filter:blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1911
1849
|
backdrop-filter:blur(var(--dshome-liquid-blur-composer)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1912
1850
|
}
|
|
1913
|
-
/* The workspace list is faded out at its bottom by a gradient whose end stop is
|
|
1914
|
-
the sidebar-fill token — an OPAQUE colour matched to the product's own sidebar
|
|
1915
|
-
background. Over a translucent glass pane that stop no longer matches what is
|
|
1916
|
-
actually behind it, so the fade shows up as a lighter band above 设置 (the
|
|
1917
|
-
user's red box). The pane itself already separates the list from the
|
|
1918
|
-
conversation, so the fade is dropped. Scoped to the sidebar and to the
|
|
1919
|
-
product's own <hash>_fade naming so nothing else is touched.
|
|
1920
|
-
NOT gated on the liquid recipe: it was, and the user sent a screenshot of the
|
|
1921
|
-
same light band in 白磨砂 ("白磨砂,有这个渐变") — the frosted pane is translucent
|
|
1922
|
-
too, so its end stop mismatches just the same. Both recipes drop it. */
|
|
1923
|
-
body .hHd-Xa_root [class*="_fade" i]{
|
|
1924
|
-
background-image:none!important;
|
|
1925
|
-
}
|
|
1926
|
-
/* The sidebar's own, heavier blur. Written after the two rules above because it
|
|
1927
|
-
ties with the dispersion one on specificity and so has to come later. */
|
|
1928
|
-
html[data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
1929
|
-
-webkit-backdrop-filter:blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1930
|
-
backdrop-filter:blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate)) brightness(var(--dshome-liquid-brightness)) contrast(var(--dshome-liquid-contrast))!important;
|
|
1931
|
-
}
|
|
1932
|
-
html[data-dshome-dispersion][data-dshome-glass="liquid"] body .hHd-Xa_root::before{
|
|
1933
|
-
-webkit-backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1934
|
-
backdrop-filter:url(#dshome-glass-dispersion) blur(var(--dshome-liquid-blur-sidebar)) saturate(var(--dshome-liquid-saturate))!important;
|
|
1935
|
-
}
|
|
1936
1851
|
/* Backdrop blur, switched off by the durable backdropBlur setting. Each glass
|
|
1937
1852
|
recipe reads its radius from a variable, so turning the frosting off is one
|
|
1938
1853
|
0px per variable -- the translucency, the sheen and the edge refraction all
|
|
@@ -1945,26 +1860,48 @@ html[data-dshome-blur="off"] body{
|
|
|
1945
1860
|
--dshome-liquid-blur-sidebar:0px;
|
|
1946
1861
|
--dshome-liquid-blur-composer:0px;
|
|
1947
1862
|
}
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
animation:none;
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1863
|
+
/* 不支持 backdrop-filter 的引擎:三个面退回不透明底。
|
|
1864
|
+
这一块 2.1.0 里也只剩半截,一并接回来。 */
|
|
1953
1865
|
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
|
|
1954
|
-
html[data-dshome-glass="liquid"] body
|
|
1955
|
-
html[data-dshome-glass="liquid"] body
|
|
1956
|
-
html[data-dshome-glass="liquid"] body .wSkVaW_header:hover::before,
|
|
1866
|
+
html[data-dshome-glass="liquid"] body :is(${SIDEBAR})::before,
|
|
1867
|
+
html[data-dshome-glass="liquid"] body ${COMPOSER_PLATE},
|
|
1957
1868
|
html[data-dshome-glass="liquid"] body :is(${USER_BUBBLE}){
|
|
1958
1869
|
background-color:rgba(255,255,255,.86)!important;
|
|
1959
1870
|
}
|
|
1960
|
-
body[data-dshome-dark]
|
|
1961
|
-
body[data-dshome-dark]
|
|
1962
|
-
body[data-dshome-dark] .wSkVaW_header:hover::before,
|
|
1871
|
+
body[data-dshome-dark] :is(${SIDEBAR})::before,
|
|
1872
|
+
body[data-dshome-dark] ${COMPOSER_PLATE},
|
|
1963
1873
|
html[data-dshome-glass="liquid"] body[data-dshome-dark] :is(${USER_BUBBLE}){
|
|
1964
1874
|
background-color:rgba(13,30,60,.92)!important;
|
|
1965
1875
|
}
|
|
1876
|
+
}`;
|
|
1877
|
+
|
|
1878
|
+
// MENU GLASS -- the popup surfaces the first two sheets never reached.
|
|
1879
|
+
//
|
|
1880
|
+
// The product's menus (agent preset, model, permission, session row, ...) are
|
|
1881
|
+
// portaled out of every pane we glass, and their own material paints an opaque
|
|
1882
|
+
// `rgba(248,249,250,.58)` backdrop. So a liquid-glass session had opaque white
|
|
1883
|
+
// menus floating over glass -- the one surface family that broke the language.
|
|
1884
|
+
//
|
|
1885
|
+
// This sheet does NOT repaint that material. It adds the glass identity on top
|
|
1886
|
+
// of it -- the hairline ring, the inner sheen and the theme's own shadow -- so
|
|
1887
|
+
// the menu keeps the product's legibility and its backdrop-filter, and simply
|
|
1888
|
+
// reads as part of the same material as the panes around it. Anchored on
|
|
1889
|
+
// `[data-menu-material]`, the product's own material hook, which survives its
|
|
1890
|
+
// class hashes being rebuilt.
|
|
1891
|
+
const MENU_GLASS_CSS = `html[data-dshome-glass="liquid"] [data-menu-material]{
|
|
1892
|
+
border-radius:var(--dsw-radius-lg);
|
|
1893
|
+
box-shadow:var(--dshome-liquid-shadow), inset 0 0 0 1px var(--dshome-liquid-ring);
|
|
1966
1894
|
}
|
|
1967
|
-
|
|
1895
|
+
html[data-dshome-glass="liquid"] [data-menu-material]>*{
|
|
1896
|
+
border-radius:inherit;
|
|
1897
|
+
}
|
|
1898
|
+
/* The product's frosted pane keeps its own look; the two extra pixels of glass
|
|
1899
|
+
identity are what it gains, gated per glass style so switching back to
|
|
1900
|
+
frosted stays the byte-identical sheet the user approved. */
|
|
1901
|
+
html[data-dshome-glass="frosted"] [data-menu-material]{
|
|
1902
|
+
border-radius:var(--dsw-radius-lg);
|
|
1903
|
+
box-shadow:inset 0 0 0 1px var(--dshome-glass-ring);
|
|
1904
|
+
}`;
|
|
1968
1905
|
|
|
1969
1906
|
/**
|
|
1970
1907
|
* Inject `css` while `anchor` (a selector, or a list of alternatives --
|
|
@@ -1985,70 +1922,29 @@ html[data-dshome-blur="off"] body{
|
|
|
1985
1922
|
* `querySelector` per frame at worst, and logs once, with the anchors it tried.
|
|
1986
1923
|
*/
|
|
1987
1924
|
function installGuarded(anchor, css) {
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
const
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
return
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
}
|
|
2003
|
-
let queued = false;
|
|
2004
|
-
let missed = false;
|
|
2005
|
-
let mo = null;
|
|
2006
|
-
const schedule = () => {
|
|
2007
|
-
if (queued || dispose !== null) return;
|
|
2008
|
-
queued = true;
|
|
2009
|
-
try {
|
|
2010
|
-
requestAnimationFrame(() => {
|
|
2011
|
-
queued = false;
|
|
2012
|
-
if (applyNow() && mo !== null) {
|
|
2013
|
-
mo.disconnect();
|
|
2014
|
-
mo = null;
|
|
2015
|
-
}
|
|
2016
|
-
});
|
|
2017
|
-
} catch (error) {
|
|
2018
|
-
queued = false;
|
|
2019
|
-
}
|
|
2020
|
-
};
|
|
2021
|
-
mo = new MutationObserver(schedule);
|
|
2022
|
-
try {
|
|
2023
|
-
mo.observe(document.body, { childList: true, subtree: true });
|
|
2024
|
-
} catch (error) {
|
|
2025
|
-
mo = null;
|
|
2026
|
-
}
|
|
2027
|
-
// One diagnostic line per block, on first sight of a shell that has
|
|
2028
|
-
// rendered and still does not carry any of the alternatives.
|
|
2029
|
-
const announce = () => {
|
|
2030
|
-
if (missed || dispose !== null) return;
|
|
2031
|
-
missed = true;
|
|
2032
|
-
console.info('[' + PLUGIN_ID + '] patch waiting: none of ' + label + ' in the DOM yet (renamed product surface? the guard keeps watching)');
|
|
1925
|
+
// 语义变体:不再「挂载时查一次」。产品的面板与行是懒挂载的,查一次会让
|
|
1926
|
+
// 晚出现的面永远拿不到样式(旧实现的 30s 超时还会悄悄放弃)。这里用
|
|
1927
|
+
// MutationObserver 持续观察,锚点一出现就注入,注入过就不再重复。
|
|
1928
|
+
const selector = Array.isArray(anchor) ? anchor.join(',') : anchor;
|
|
1929
|
+
let injected = false;
|
|
1930
|
+
const style = document.createElement('style');
|
|
1931
|
+
style.dataset.plugin = PLUGIN_ID;
|
|
1932
|
+
style.dataset.pluginCss = PLUGIN_ID + '/patch.' + selector.slice(0, 40);
|
|
1933
|
+
style.textContent = css;
|
|
1934
|
+
const tryInject = () => {
|
|
1935
|
+
if (injected || document.querySelector(selector) === null) return;
|
|
1936
|
+
document.head.appendChild(style);
|
|
1937
|
+
injected = true;
|
|
1938
|
+
observer.disconnect();
|
|
2033
1939
|
};
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
}, 15000);
|
|
1940
|
+
const observer = new MutationObserver(() => { tryInject(); });
|
|
1941
|
+
tryInject();
|
|
1942
|
+
if (!injected) observer.observe(document.documentElement, { childList: true, subtree: true });
|
|
2038
1943
|
return () => {
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
if (dispose !== null) dispose();
|
|
1944
|
+
observer.disconnect();
|
|
1945
|
+
style.remove();
|
|
2042
1946
|
};
|
|
2043
1947
|
}
|
|
2044
|
-
|
|
2045
|
-
/**
|
|
2046
|
-
* Own the light/dark marker: reflect the theme service's active color
|
|
2047
|
-
* scheme onto our own `data-dshome-dark` attribute, so dark styling never
|
|
2048
|
-
* depends on the product's attribute name. Falls back to mirroring the
|
|
2049
|
-
* product attribute only when the theme service or the `theme/change`
|
|
2050
|
-
* subscription is unavailable.
|
|
2051
|
-
*/
|
|
2052
1948
|
function createDarkSync(ctx, theme) {
|
|
2053
1949
|
/** Last value we wrote to the body; null until the first apply(). */
|
|
2054
1950
|
let lastDark = null;
|
|
@@ -2415,7 +2311,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2415
2311
|
* `.gdEzaW_bubble` this list used to carry matches nothing on the current
|
|
2416
2312
|
* build, which is how the old entry went dead without anyone noticing.
|
|
2417
2313
|
*/
|
|
2418
|
-
const SPOT_SURFACES = `${USER_BUBBLE},.
|
|
2314
|
+
const SPOT_SURFACES = `${USER_BUBBLE},${COMPOSER_PLATE},.dshome-sa,.dshome-file-menu,${SIDEBAR}`;
|
|
2419
2315
|
function startSpecularSpotter() {
|
|
2420
2316
|
const onOver = (event) => {
|
|
2421
2317
|
const target = event.target;
|
|
@@ -2480,9 +2376,9 @@ html[data-dshome-blur="off"] body{
|
|
|
2480
2376
|
// (an OS setting that disables the effect and looks like a bug) and
|
|
2481
2377
|
// `spots` (0 means the composer no longer matches TILT_SELECTOR), and the
|
|
2482
2378
|
// reduced-motion case logs one info line instead of vanishing.
|
|
2483
|
-
const TILT_SELECTOR =
|
|
2379
|
+
const TILT_SELECTOR = COMPOSER_PLATE;
|
|
2484
2380
|
/** On <html> while the composer leans; the lens touch on the line under the
|
|
2485
|
-
* composer reads it (see the [data-
|
|
2381
|
+
* composer reads it (see the [data-composer-stats] patch block). */
|
|
2486
2382
|
const LEAN_ATTR = 'data-dshome-leaning';
|
|
2487
2383
|
// 0.0175 rad was upstream's conservative lean, and after 2.0.85 finally made
|
|
2488
2384
|
// the effect reachable the verdict was "倾斜效果很微弱" -- fair, at ~4 degrees
|
|
@@ -2572,10 +2468,15 @@ html[data-dshome-blur="off"] body{
|
|
|
2572
2468
|
raf = requestAnimationFrame(() => {
|
|
2573
2469
|
raf = 0;
|
|
2574
2470
|
if (current !== spot || frozen) return;
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2471
|
+
const g = spotGeometry(spot, rect);
|
|
2472
|
+
if (g.halfW <= 0 || g.halfH <= 0) return;
|
|
2473
|
+
if (clientX < g.left || clientX > g.right || clientY < g.top || clientY > g.bottom) return;
|
|
2474
|
+
// Same half-range clamp as before -- `(clientX - left) / width - 0.5`
|
|
2475
|
+
// is `(clientX - centre) / width` -- but over a width that cannot
|
|
2476
|
+
// change because of what this line is about to write.
|
|
2477
|
+
const dx = Math.min(0.5, Math.max(-0.5, (clientX - g.cx) / (g.halfW * 2)));
|
|
2478
|
+
const dy = Math.min(0.5, Math.max(-0.5, (clientY - g.cy) / (g.halfH * 2)));
|
|
2479
|
+
spot.style.transformOrigin = g.halfW + 'px ' + g.halfH + 'px';
|
|
2579
2480
|
spot.style.transition = TILT_TRANSITION;
|
|
2580
2481
|
spot.style.transform = 'perspective(' + TILT_PERSPECTIVE + 'px)'
|
|
2581
2482
|
+ ' rotateX(' + (TILT_MAX * -2 * dy).toFixed(5) + 'rad)'
|
|
@@ -2600,11 +2501,39 @@ html[data-dshome-blur="off"] body{
|
|
|
2600
2501
|
* broken effect: the box just never leans. Geometry is the one thing the
|
|
2601
2502
|
* theme can measure for itself.
|
|
2602
2503
|
*/
|
|
2504
|
+
/**
|
|
2505
|
+
* Transform-independent geometry for one tilt spot.
|
|
2506
|
+
*
|
|
2507
|
+
* `getBoundingClientRect()` INCLUDES the transform this effect writes, so
|
|
2508
|
+
* feeding it back in closes a loop: the scale resizes the rect, the rect
|
|
2509
|
+
* normalises the cursor offset, the offset picks the next angle, and the
|
|
2510
|
+
* angle moves the rect again. Tuned against the short composer that loop is
|
|
2511
|
+
* a pixel or two and invisible; the moment a long draft grows the composer to
|
|
2512
|
+
* its max-height the same RELATIVE feedback is ~10px, and the card visibly
|
|
2513
|
+
* flaps between two sizes a few times a second. That is the same hover loop
|
|
2514
|
+
* 2.0.79 fought by dropping `TILT_SCALE` to 1 -- and it came back with the
|
|
2515
|
+
* 1.015 lift, because the loop was damped, never broken.
|
|
2516
|
+
*
|
|
2517
|
+
* The centre is the one part of the rect a transform about the centre cannot
|
|
2518
|
+
* move (under `perspective()` the centre plane sits at z=0, so the projection
|
|
2519
|
+
* leaves it alone), and `offsetWidth`/`offsetHeight` never include
|
|
2520
|
+
* transforms. Those two facts make every use below a function of the pointer
|
|
2521
|
+
* and the LAYOUT box alone: nothing reads last frame's transform, so there is
|
|
2522
|
+
* no loop left to damp -- and the lift can stay at full strength.
|
|
2523
|
+
*/
|
|
2524
|
+
const spotGeometry = (spot, rect) => {
|
|
2525
|
+
const halfW = spot.offsetWidth / 2;
|
|
2526
|
+
const halfH = spot.offsetHeight / 2;
|
|
2527
|
+
const cx = rect.left + rect.width / 2;
|
|
2528
|
+
const cy = rect.top + rect.height / 2;
|
|
2529
|
+
return { cx, cy, halfW, halfH, left: cx - halfW, right: cx + halfW, top: cy - halfH, bottom: cy + halfH };
|
|
2530
|
+
};
|
|
2603
2531
|
const spotAt = (clientX, clientY) => {
|
|
2604
2532
|
for (const spot of document.querySelectorAll(TILT_SELECTOR)) {
|
|
2605
2533
|
const rect = spot.getBoundingClientRect();
|
|
2606
|
-
|
|
2607
|
-
if (
|
|
2534
|
+
const g = spotGeometry(spot, rect);
|
|
2535
|
+
if (g.halfW <= 0 || g.halfH <= 0) continue;
|
|
2536
|
+
if (clientX < g.left || clientX > g.right || clientY < g.top || clientY > g.bottom) continue;
|
|
2608
2537
|
return { spot, rect };
|
|
2609
2538
|
}
|
|
2610
2539
|
return null;
|
|
@@ -2626,7 +2555,8 @@ html[data-dshome-blur="off"] body{
|
|
|
2626
2555
|
const spot = target.closest(TILT_SELECTOR);
|
|
2627
2556
|
if (spot === null) return;
|
|
2628
2557
|
const rect = spot.getBoundingClientRect();
|
|
2629
|
-
|
|
2558
|
+
const g = spotGeometry(spot, rect);
|
|
2559
|
+
if (event.clientX < g.left || event.clientX > g.right || event.clientY < g.top || event.clientY > g.bottom) return;
|
|
2630
2560
|
if (controlAt(event.clientX, event.clientY)) {
|
|
2631
2561
|
if (current === spot) frozen = true;
|
|
2632
2562
|
return;
|
|
@@ -2648,9 +2578,9 @@ html[data-dshome-blur="off"] body{
|
|
|
2648
2578
|
// the lean's own movement must not be able to hand the card back
|
|
2649
2579
|
// and forth under a stationary pointer.
|
|
2650
2580
|
if (current !== null) {
|
|
2651
|
-
const
|
|
2652
|
-
const outside = event.clientX <
|
|
2653
|
-
|| event.clientY <
|
|
2581
|
+
const g = spotGeometry(current, current.getBoundingClientRect());
|
|
2582
|
+
const outside = event.clientX < g.left - TILT_RELEASE_PAD || event.clientX > g.right + TILT_RELEASE_PAD
|
|
2583
|
+
|| event.clientY < g.top - TILT_RELEASE_PAD || event.clientY > g.bottom + TILT_RELEASE_PAD;
|
|
2654
2584
|
if (outside) {
|
|
2655
2585
|
release(current);
|
|
2656
2586
|
current = null;
|
|
@@ -2681,9 +2611,9 @@ html[data-dshome-blur="off"] body{
|
|
|
2681
2611
|
};
|
|
2682
2612
|
const onOut = (event) => {
|
|
2683
2613
|
if (current === null) return;
|
|
2684
|
-
const
|
|
2685
|
-
if (event.clientX >=
|
|
2686
|
-
&& event.clientY >=
|
|
2614
|
+
const g = spotGeometry(current, current.getBoundingClientRect());
|
|
2615
|
+
if (event.clientX >= g.left - TILT_RELEASE_PAD && event.clientX <= g.right + TILT_RELEASE_PAD
|
|
2616
|
+
&& event.clientY >= g.top - TILT_RELEASE_PAD && event.clientY <= g.bottom + TILT_RELEASE_PAD) return;
|
|
2687
2617
|
release(current);
|
|
2688
2618
|
current = null;
|
|
2689
2619
|
};
|
|
@@ -2721,7 +2651,8 @@ html[data-dshome-blur="off"] body{
|
|
|
2721
2651
|
if (!supportsWebgl2()) return startParticles();
|
|
2722
2652
|
// Which build is running, readable from the page (see README). One line in
|
|
2723
2653
|
// the console answers "is the bundle I just installed actually live?".
|
|
2724
|
-
|
|
2654
|
+
/* 版本标记统一在激活处写(见 window.__dshomeAnchors 旁边):这里是两条不同的
|
|
2655
|
+
挂载路径,各自写一个会互相覆盖,2.1.0 的包曾经报成 2.0.89 就是这个原因。 */
|
|
2725
2656
|
const canvas = document.createElement('canvas');
|
|
2726
2657
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2727
2658
|
const style = canvas.style;
|
|
@@ -2874,7 +2805,7 @@ html[data-dshome-blur="off"] body{
|
|
|
2874
2805
|
washLayer: getComputedStyle(document.body, '::before').position,
|
|
2875
2806
|
tiltCard: card === null ? null : getComputedStyle(card).backdropFilter,
|
|
2876
2807
|
tiltCardWillChange: card === null ? null : (getComputedStyle(card).willChange || 'auto'),
|
|
2877
|
-
sidebar: chain(
|
|
2808
|
+
sidebar: chain(SIDEBAR, '::before'),
|
|
2878
2809
|
bubble: chain(USER_BUBBLE)
|
|
2879
2810
|
};
|
|
2880
2811
|
};
|
|
@@ -2912,7 +2843,8 @@ html[data-dshome-blur="off"] body{
|
|
|
2912
2843
|
}
|
|
2913
2844
|
|
|
2914
2845
|
function startParticles() {
|
|
2915
|
-
|
|
2846
|
+
/* 版本标记统一在激活处写(见 window.__dshomeAnchors 旁边):这里是两条不同的
|
|
2847
|
+
挂载路径,各自写一个会互相覆盖,2.1.0 的包曾经报成 2.0.89 就是这个原因。 */
|
|
2916
2848
|
const canvas = document.createElement('canvas');
|
|
2917
2849
|
canvas.setAttribute('data-dsh-deepseek-canvas', '');
|
|
2918
2850
|
const style = canvas.style;
|
|
@@ -3092,340 +3024,38 @@ html[data-dshome-blur="off"] body{
|
|
|
3092
3024
|
return () => document.removeEventListener('click', onClick, true);
|
|
3093
3025
|
}
|
|
3094
3026
|
|
|
3095
|
-
function watchHeaderInteraction() {
|
|
3096
|
-
const EASE = 'cubic-bezier(.2,.8,.2,1)';
|
|
3097
|
-
const DUR = '.3s';
|
|
3098
|
-
const timers = [];
|
|
3099
|
-
|
|
3100
|
-
const headerOf = (node) => (node && node.closest ? node.closest('.wSkVaW_header') : null);
|
|
3101
|
-
const isTrajectory = (header) => {
|
|
3102
|
-
const root = header.closest('.wSkVaW_root');
|
|
3103
|
-
return root !== null && root.querySelector('.fV0t5q_root') !== null;
|
|
3104
|
-
};
|
|
3105
|
-
|
|
3106
|
-
const enter = (header) => {
|
|
3107
|
-
const actions = header.querySelector('.wSkVaW_headerActions');
|
|
3108
|
-
const crumbs = header.querySelector('.wSkVaW_crumbs');
|
|
3109
|
-
if (crumbs === null) return;
|
|
3110
|
-
|
|
3111
|
-
const actionsFirst = actions ? actions.getBoundingClientRect() : null;
|
|
3112
|
-
const crumbsFirst = crumbs.getBoundingClientRect();
|
|
3113
|
-
|
|
3114
|
-
header.classList.add('dshome-swap');
|
|
3115
|
-
|
|
3116
|
-
const actionsFlow = actions ? actions.getBoundingClientRect() : null;
|
|
3117
|
-
const crumbsFlow = crumbs.getBoundingClientRect();
|
|
3118
|
-
|
|
3119
|
-
if (actions && actionsFirst && actionsFlow) {
|
|
3120
|
-
const dx = actionsFirst.left - actionsFlow.left;
|
|
3121
|
-
const dy = actionsFirst.top - actionsFlow.top;
|
|
3122
|
-
actions.style.transition = 'none';
|
|
3123
|
-
actions.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
|
|
3124
|
-
}
|
|
3125
|
-
|
|
3126
|
-
const cdx = crumbsFirst.left - crumbsFlow.left;
|
|
3127
|
-
const cdy = crumbsFirst.top - crumbsFlow.top;
|
|
3128
|
-
crumbs.style.transition = 'none';
|
|
3129
|
-
crumbs.style.transform = 'translate(' + cdx + 'px,' + cdy + 'px)';
|
|
3130
|
-
|
|
3131
|
-
void header.offsetWidth;
|
|
3132
|
-
|
|
3133
|
-
if (actions) {
|
|
3134
|
-
actions.style.transition = 'transform ' + DUR + ' ' + EASE;
|
|
3135
|
-
actions.style.transform = 'translate(0,0)';
|
|
3136
|
-
}
|
|
3137
|
-
crumbs.style.transition = 'transform ' + DUR + ' ' + EASE;
|
|
3138
|
-
crumbs.style.transform = 'translate(0,0)';
|
|
3139
|
-
|
|
3140
|
-
const timer = setTimeout(() => {
|
|
3141
|
-
if (actions) { actions.style.transition = ''; actions.style.transform = ''; }
|
|
3142
|
-
crumbs.style.transition = '';
|
|
3143
|
-
crumbs.style.transform = '';
|
|
3144
|
-
}, 320);
|
|
3145
|
-
timers.push(timer);
|
|
3146
|
-
};
|
|
3147
|
-
|
|
3148
|
-
const leave = (header) => {
|
|
3149
|
-
const actions = header.querySelector('.wSkVaW_headerActions');
|
|
3150
|
-
const crumbs = header.querySelector('.wSkVaW_crumbs');
|
|
3151
|
-
|
|
3152
|
-
const actionsHover = actions ? actions.getBoundingClientRect() : null;
|
|
3153
|
-
const crumbsHover = crumbs ? crumbs.getBoundingClientRect() : null;
|
|
3154
|
-
|
|
3155
|
-
header.classList.remove('dshome-swap');
|
|
3156
|
-
|
|
3157
|
-
if (actions && actionsHover) {
|
|
3158
|
-
const n = actions.getBoundingClientRect();
|
|
3159
|
-
const dx = actionsHover.left - n.left;
|
|
3160
|
-
const dy = actionsHover.top - n.top;
|
|
3161
|
-
actions.style.transition = 'none';
|
|
3162
|
-
actions.style.transform = 'translate(' + dx + 'px,' + dy + 'px)';
|
|
3163
|
-
}
|
|
3164
|
-
|
|
3165
|
-
if (crumbs && crumbsHover) {
|
|
3166
|
-
const n = crumbs.getBoundingClientRect();
|
|
3167
|
-
const cdx = crumbsHover.left - n.left;
|
|
3168
|
-
const cdy = crumbsHover.top - n.top;
|
|
3169
|
-
crumbs.style.transition = 'none';
|
|
3170
|
-
crumbs.style.transform = 'translate(' + cdx + 'px,' + cdy + 'px)';
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
|
-
void header.offsetWidth;
|
|
3174
|
-
|
|
3175
|
-
if (actions) {
|
|
3176
|
-
actions.style.transition = 'transform ' + DUR + ' ' + EASE;
|
|
3177
|
-
actions.style.transform = 'translate(0,0)';
|
|
3178
|
-
}
|
|
3179
|
-
if (crumbs) {
|
|
3180
|
-
crumbs.style.transition = 'transform ' + DUR + ' ' + EASE;
|
|
3181
|
-
crumbs.style.transform = 'translate(0,0)';
|
|
3182
|
-
}
|
|
3183
|
-
|
|
3184
|
-
const timer = setTimeout(() => {
|
|
3185
|
-
if (actions) { actions.style.transition = ''; actions.style.transform = ''; }
|
|
3186
|
-
if (crumbs) { crumbs.style.transition = ''; crumbs.style.transform = ''; }
|
|
3187
|
-
}, 320);
|
|
3188
|
-
timers.push(timer);
|
|
3189
|
-
};
|
|
3190
|
-
|
|
3191
|
-
const over = (event) => {
|
|
3192
|
-
const header = headerOf(event.target);
|
|
3193
|
-
if (header === null || isTrajectory(header)) return;
|
|
3194
|
-
if (header.classList.contains('dshome-swap')) return;
|
|
3195
|
-
const from = event.relatedTarget;
|
|
3196
|
-
if (from && header.contains(from)) return;
|
|
3197
|
-
enter(header);
|
|
3198
|
-
};
|
|
3199
|
-
|
|
3200
|
-
const out = (event) => {
|
|
3201
|
-
const header = headerOf(event.target);
|
|
3202
|
-
if (header === null) return;
|
|
3203
|
-
if (!header.classList.contains('dshome-swap')) return;
|
|
3204
|
-
const to = event.relatedTarget;
|
|
3205
|
-
if (to && header.contains(to)) return;
|
|
3206
|
-
leave(header);
|
|
3207
|
-
};
|
|
3208
|
-
|
|
3209
|
-
document.addEventListener('mouseover', over);
|
|
3210
|
-
document.addEventListener('mouseout', out);
|
|
3211
|
-
|
|
3212
|
-
return () => {
|
|
3213
|
-
document.removeEventListener('mouseover', over);
|
|
3214
|
-
document.removeEventListener('mouseout', out);
|
|
3215
|
-
timers.forEach((t) => clearTimeout(t));
|
|
3216
|
-
document.querySelectorAll('.wSkVaW_header.dshome-swap').forEach((h) => {
|
|
3217
|
-
h.classList.remove('dshome-swap');
|
|
3218
|
-
const a = h.querySelector('.wSkVaW_headerActions');
|
|
3219
|
-
const c = h.querySelector('.wSkVaW_crumbs');
|
|
3220
|
-
if (a) { a.style.transition = ''; a.style.transform = ''; }
|
|
3221
|
-
if (c) { c.style.transition = ''; c.style.transform = ''; }
|
|
3222
|
-
});
|
|
3223
|
-
};
|
|
3224
|
-
}
|
|
3225
|
-
|
|
3226
|
-
/**
|
|
3227
|
-
* Add an "Open workspace" entry to the folder row's (workspace row) three-dot
|
|
3228
|
-
* menu. The workspace browser renders its row menus internally with no plugin
|
|
3229
|
-
* slot, so the theme patches the popup DOM: it tracks the ellipsis anchor of a
|
|
3230
|
-
* project row, and when the portaled [role=menu] appears it inserts an item
|
|
3231
|
-
* that resolves the row's workspace (title match against the workspaces
|
|
3232
|
-
* store, DOM-order fallback) and asks the host half (connection RPC) to open
|
|
3233
|
-
* the workspace's directory with the OS file manager in the foreground.
|
|
3234
|
-
* The private host channel is the primary reveal path; `workspaces.openPath`
|
|
3235
|
-
* is used only when it still exists (dsh ≤0.1.1), and a failed reveal is
|
|
3236
|
-
* reported on the console instead of failing silently.
|
|
3237
|
-
*/
|
|
3238
|
-
function watchWorkspaceMenu(ctx) {
|
|
3239
|
-
let workspaces;
|
|
3240
|
-
try {
|
|
3241
|
-
workspaces = ctx.get('workspaces');
|
|
3242
|
-
} catch (error) {
|
|
3243
|
-
workspaces = undefined;
|
|
3244
|
-
}
|
|
3245
|
-
let connection;
|
|
3246
|
-
try {
|
|
3247
|
-
connection = ctx.get('connection');
|
|
3248
|
-
} catch (error) {
|
|
3249
|
-
connection = undefined;
|
|
3250
|
-
}
|
|
3251
|
-
if (workspaces === undefined || workspaces === null || workspaces.list === undefined) return () => {};
|
|
3252
3027
|
|
|
3253
|
-
const ROW = '.YDXeBa_projectRow';
|
|
3254
|
-
const ACTIONS = '.YDXeBa_rowActions';
|
|
3255
|
-
const LABEL = '.YDXeBa_title';
|
|
3256
|
-
const CLS = 'dshome-ws-open';
|
|
3257
|
-
const FOLDER_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
|
|
3258
|
-
|
|
3259
|
-
let label = '打开工作区';
|
|
3260
|
-
try {
|
|
3261
|
-
const locale = ctx.get('locale');
|
|
3262
|
-
if (locale !== undefined) {
|
|
3263
|
-
const active = locale.getLocale().active;
|
|
3264
|
-
if (active !== 'zh' && active !== 'zh-CN' && active !== 'zh-Hans') label = 'Open workspace';
|
|
3265
|
-
}
|
|
3266
|
-
} catch (error) {}
|
|
3267
|
-
|
|
3268
|
-
let anchorRow = null;
|
|
3269
|
-
|
|
3270
|
-
const resolveWorkspace = (row) => {
|
|
3271
|
-
try {
|
|
3272
|
-
const items = workspaces.list.getSnapshot().items;
|
|
3273
|
-
if (!Array.isArray(items)) return undefined;
|
|
3274
|
-
const titleEl = row.querySelector(LABEL);
|
|
3275
|
-
const labelText = titleEl === null ? '' : (titleEl.textContent || '').trim();
|
|
3276
|
-
if (labelText === '') return undefined;
|
|
3277
|
-
const byTitle = items.filter((w) => w !== null && w !== undefined && w.title === labelText);
|
|
3278
|
-
if (byTitle.length === 1) return byTitle[0];
|
|
3279
|
-
const rows = Array.from(document.querySelectorAll(ROW));
|
|
3280
|
-
const index = rows.indexOf(row);
|
|
3281
|
-
if (index === -1) return undefined;
|
|
3282
|
-
let ordinal = 0;
|
|
3283
|
-
for (let i = 0; i < index; i++) {
|
|
3284
|
-
const t = rows[i].querySelector(LABEL);
|
|
3285
|
-
const l = t === null ? '' : (t.textContent || '').trim();
|
|
3286
|
-
if (l !== '' && l !== 'Ungrouped') ordinal += 1;
|
|
3287
|
-
}
|
|
3288
|
-
const candidate = items[ordinal];
|
|
3289
|
-
return candidate === undefined || candidate === null ? undefined : candidate;
|
|
3290
|
-
} catch (error) {
|
|
3291
|
-
return undefined;
|
|
3292
|
-
}
|
|
3293
|
-
};
|
|
3294
|
-
|
|
3295
|
-
const track = (target) => {
|
|
3296
|
-
anchorRow = null;
|
|
3297
|
-
if (target === null || typeof target.closest !== 'function') return;
|
|
3298
|
-
const button = target.closest('button');
|
|
3299
|
-
if (button === null) return;
|
|
3300
|
-
const actions = button.closest(ACTIONS);
|
|
3301
|
-
if (actions === null) return;
|
|
3302
|
-
const row = actions.closest(ROW);
|
|
3303
|
-
if (row === null) return;
|
|
3304
|
-
if (button !== actions.querySelector('button')) return;
|
|
3305
|
-
anchorRow = row;
|
|
3306
|
-
};
|
|
3307
|
-
|
|
3308
|
-
const onClickCapture = (event) => track(event.target);
|
|
3309
|
-
const onFocusIn = (event) => track(event.target);
|
|
3310
|
-
document.addEventListener('click', onClickCapture, true);
|
|
3311
|
-
document.addEventListener('focusin', onFocusIn, true);
|
|
3312
|
-
|
|
3313
|
-
const injectItem = (menu) => {
|
|
3314
|
-
if (menu === null || menu.querySelector('.' + CLS) !== null) return;
|
|
3315
|
-
const viewport = menu.querySelector('[role="presentation"]');
|
|
3316
|
-
if (viewport === null) return;
|
|
3317
|
-
const row = anchorRow;
|
|
3318
|
-
if (row === null || !document.contains(row)) return;
|
|
3319
|
-
const workspace = resolveWorkspace(row);
|
|
3320
|
-
if (workspace === undefined) return;
|
|
3321
|
-
|
|
3322
|
-
const wrap = document.createElement('div');
|
|
3323
|
-
wrap.className = CLS + '-wrap';
|
|
3324
|
-
const item = document.createElement('button');
|
|
3325
|
-
item.type = 'button';
|
|
3326
|
-
item.className = CLS;
|
|
3327
|
-
item.setAttribute('role', 'menuitem');
|
|
3328
|
-
item.title = label;
|
|
3329
|
-
item.setAttribute('aria-label', label);
|
|
3330
|
-
const icon = document.createElement('span');
|
|
3331
|
-
icon.className = CLS + '-icon';
|
|
3332
|
-
icon.innerHTML = FOLDER_SVG;
|
|
3333
|
-
const text = document.createElement('span');
|
|
3334
|
-
text.className = CLS + '-label';
|
|
3335
|
-
text.textContent = label;
|
|
3336
|
-
item.appendChild(icon);
|
|
3337
|
-
item.appendChild(text);
|
|
3338
|
-
item.addEventListener('click', (event) => {
|
|
3339
|
-
event.preventDefault();
|
|
3340
|
-
event.stopPropagation();
|
|
3341
|
-
const path = workspace.path;
|
|
3342
|
-
// The host bridge is a same-origin webServer route (see bridgeCall).
|
|
3343
|
-
const fallback = (reason) => {
|
|
3344
|
-
if (typeof workspaces.openPath === 'function') {
|
|
3345
|
-
try {
|
|
3346
|
-
workspaces.openPath(path);
|
|
3347
|
-
return;
|
|
3348
|
-
} catch (error) {
|
|
3349
|
-
console.warn('[deepseek-style-theme] workspaces.openPath failed:', error);
|
|
3350
|
-
}
|
|
3351
|
-
}
|
|
3352
|
-
console.warn(
|
|
3353
|
-
'[deepseek-style-theme] 打开工作区失败(' + reason + '):' +
|
|
3354
|
-
'私有 RPC 通道不可用,且当前 dsh 版本已移除 workspaces.openPath。' +
|
|
3355
|
-
'请确认插件为 1.38.4+ 并重启 dsh web(旧版宿主端因缺少 webServer 注入无法注册通道)。'
|
|
3356
|
-
);
|
|
3357
|
-
};
|
|
3358
|
-
Promise.resolve(bridgeCall('dshome/explorer.open', { path: path }))
|
|
3359
|
-
.then((result) => {
|
|
3360
|
-
if (result !== undefined && result !== null && result.ok === true) return;
|
|
3361
|
-
const detail = result !== undefined && result !== null && result.error !== undefined
|
|
3362
|
-
? String(result.error.message)
|
|
3363
|
-
: 'no result';
|
|
3364
|
-
fallback('bridge returned ' + detail);
|
|
3365
|
-
})
|
|
3366
|
-
.catch((error) => {
|
|
3367
|
-
fallback('bridge call threw: ' + (error instanceof Error ? error.message : String(error)));
|
|
3368
|
-
});
|
|
3369
|
-
try {
|
|
3370
|
-
document.body.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, cancelable: true }));
|
|
3371
|
-
} catch (error) {}
|
|
3372
|
-
});
|
|
3373
|
-
wrap.appendChild(item);
|
|
3374
|
-
viewport.insertBefore(wrap, viewport.firstChild);
|
|
3375
|
-
};
|
|
3376
|
-
|
|
3377
|
-
const observer = new MutationObserver((records) => {
|
|
3378
|
-
for (const record of records) {
|
|
3379
|
-
for (const node of record.addedNodes) {
|
|
3380
|
-
if (!(node instanceof Element)) continue;
|
|
3381
|
-
if (node.matches('[role="menu"]')) {
|
|
3382
|
-
injectItem(node);
|
|
3383
|
-
continue;
|
|
3384
|
-
}
|
|
3385
|
-
node.querySelectorAll('[role="menu"]').forEach(injectItem);
|
|
3386
|
-
}
|
|
3387
|
-
}
|
|
3388
|
-
});
|
|
3389
|
-
observer.observe(document.body, { childList: true, subtree: true });
|
|
3390
|
-
|
|
3391
|
-
return () => {
|
|
3392
|
-
observer.disconnect();
|
|
3393
|
-
document.removeEventListener('click', onClickCapture, true);
|
|
3394
|
-
document.removeEventListener('focusin', onFocusIn, true);
|
|
3395
|
-
};
|
|
3396
|
-
}
|
|
3397
3028
|
|
|
3398
3029
|
/**
|
|
3399
3030
|
* Action menu for the product's delivered/changed files
|
|
3400
3031
|
* (`@deepseek-ai/dsh-client-ui-deliverables`), which renders two separate
|
|
3401
3032
|
* surfaces:
|
|
3402
3033
|
*
|
|
3403
|
-
* - `[data-
|
|
3034
|
+
* - `[data-presented-files-row]` — the "本轮文件改动" chips, one
|
|
3404
3035
|
* `button[title=absolutePath]` per changed path, whose only own action
|
|
3405
3036
|
* is the product's preview opener;
|
|
3406
3037
|
* - `[data-presented-file]` — the declared-file cards, whose overlay button
|
|
3407
3038
|
* previews and whose chevron already offers default-app open and reveal.
|
|
3408
3039
|
*
|
|
3409
|
-
* Both
|
|
3410
|
-
* (
|
|
3411
|
-
*
|
|
3412
|
-
*
|
|
3413
|
-
*
|
|
3414
|
-
* reading a `title` tooltip by hand — so it is also added to the product's
|
|
3415
|
-
* own chevron menu.
|
|
3040
|
+
* Both open this menu. The product's own action is preserved as a menu item
|
|
3041
|
+
* ("打开文件" re-dispatches the element's own click), so taking over the
|
|
3042
|
+
* gesture trades nothing away. The absolute path rides in each surface's
|
|
3043
|
+
* `title` (a card via the product's own `resolveWorkspacePath`), so this
|
|
3044
|
+
* patch never re-derives a workspace.
|
|
3416
3045
|
*
|
|
3417
|
-
*
|
|
3418
|
-
*
|
|
3419
|
-
*
|
|
3420
|
-
*
|
|
3046
|
+
* 2.1.0 收敛:`[data-produced-files-row]` 是 0.1.7 前后的旧名(0.2.0 已改为
|
|
3047
|
+
* `data-presented-files-row`,旧名在整个安装目录里都不存在了),双名兼容已移除;
|
|
3048
|
+
* 行内 `@file` 引用(`button[data-ref-chip]`)的点击接管也已撤掉——那一类交还
|
|
3049
|
+
* 官方行为(点击即官方预览器)。
|
|
3421
3050
|
*/
|
|
3422
3051
|
function watchFileCardMenu(ctx) {
|
|
3423
3052
|
const CARD = '[data-presented-file]';
|
|
3424
3053
|
// The product renders changed files through a *different* component:
|
|
3425
3054
|
// ProducedFiles draws one `button[title=absolutePath]` per path inside
|
|
3426
|
-
// `
|
|
3427
|
-
// `[data-presented-file]` cards at all, so they need
|
|
3428
|
-
|
|
3055
|
+
// the changed-files row (0.2.0's attribute is `data-presented-files-row`).
|
|
3056
|
+
// Those chips are not `[data-presented-file]` cards at all, so they need
|
|
3057
|
+
// their own anchor.
|
|
3058
|
+
const PRODUCED = '[data-presented-files-row] button[title]';
|
|
3429
3059
|
const MENU = '[role="menu"]';
|
|
3430
3060
|
const CLS = 'dshome-file-menu';
|
|
3431
3061
|
const ITEM_CLS = CLS + '-item';
|
|
@@ -3435,18 +3065,30 @@ html[data-dshome-blur="off"] body{
|
|
|
3435
3065
|
const ICON_REVEAL = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/></svg>';
|
|
3436
3066
|
const ICON_COPY = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>';
|
|
3437
3067
|
const ICON_PREVIEW = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>';
|
|
3438
|
-
const CSS =
|
|
3439
|
-
|
|
3440
|
-
body[data-dshome-dark] .${CLS}
|
|
3441
|
-
|
|
3442
|
-
.${ITEM_CLS}
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
.${ITEM_CLS}
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3068
|
+
const CSS = `.${CLS}
|
|
3069
|
+
{position:fixed;z-index:2147483000;min-width:198px;padding:6px;border-radius:14px;background:rgba(255,255,255,.94);border:.5px solid var(--dsw-alias-border-l2,rgba(0,0,0,.1));box-shadow:0 18px 44px rgba(15,30,60,.2);backdrop-filter:blur(18px) saturate(1.4);-webkit-backdrop-filter:blur(18px) saturate(1.4);animation:dshome-menu-pop .16s cubic-bezier(.2,.8,.2,1);transform-origin:top left}
|
|
3070
|
+
body[data-dshome-dark] .${CLS}
|
|
3071
|
+
{background:rgba(14,30,60,.94);box-shadow:0 18px 44px rgba(0,0,0,.5)}
|
|
3072
|
+
.${ITEM_CLS}
|
|
3073
|
+
,.${COPY_CLS}
|
|
3074
|
+
{display:flex;align-items:center;gap:9px;box-sizing:border-box;width:100%;padding:8px 10px;border:0;border-radius:9px;background:transparent;color:var(--dsw-alias-label-primary,#152443);font:inherit;font-size:13px;line-height:20px;text-align:left;cursor:pointer;transition:background-color .12s ease,transform .12s ease}
|
|
3075
|
+
.${ITEM_CLS}
|
|
3076
|
+
:hover,.${COPY_CLS}
|
|
3077
|
+
:hover{background:color-mix(in srgb,var(--ds-brand,#4d6bfe) 13%,transparent);transform:translateX(1px)}
|
|
3078
|
+
.${ITEM_CLS}
|
|
3079
|
+
:focus-visible,.${COPY_CLS}
|
|
3080
|
+
:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#4d6bfe);outline-offset:-2px}
|
|
3081
|
+
.${ITEM_CLS}
|
|
3082
|
+
svg,.${COPY_CLS}
|
|
3083
|
+
svg{flex:none;width:15px;height:15px;opacity:.72}
|
|
3084
|
+
.${ITEM_CLS}
|
|
3085
|
+
>span,.${COPY_CLS}
|
|
3086
|
+
>span{display:inline-flex;align-items:center;min-width:0}
|
|
3087
|
+
.${CLS}
|
|
3088
|
+
-sep{height:1px;margin:5px 6px;background:var(--dsw-alias-border-l1,rgba(0,0,0,.06))}
|
|
3089
|
+
.${TOAST_CLS}
|
|
3090
|
+
{position:fixed;z-index:2147483001;left:50%;bottom:56px;transform:translateX(-50%);padding:8px 15px;border-radius:999px;background:rgba(20,36,70,.93);color:#fff;font-size:12px;line-height:18px;pointer-events:none;animation:dshome-fade-in .18s ease}
|
|
3091
|
+
@keyframes dshome-menu-pop{from{opacity:0;transform:scale(.96) translateY(-4px)}to{opacity:1;transform:none}}`;
|
|
3450
3092
|
|
|
3451
3093
|
const isZh = () => {
|
|
3452
3094
|
try {
|
|
@@ -3459,6 +3101,7 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3459
3101
|
}
|
|
3460
3102
|
};
|
|
3461
3103
|
const labels = () => (isZh() ? {
|
|
3104
|
+
openOfficial: '打开文件',
|
|
3462
3105
|
open: '用默认应用打开',
|
|
3463
3106
|
reveal: '打开所在文件夹',
|
|
3464
3107
|
copy: '复制路径',
|
|
@@ -3468,6 +3111,7 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3468
3111
|
copyDenied: '浏览器拒绝了剪贴板访问,请手动复制',
|
|
3469
3112
|
failed: '操作失败(宿主端未响应)'
|
|
3470
3113
|
} : {
|
|
3114
|
+
openOfficial: 'Open file',
|
|
3471
3115
|
open: 'Open with default app',
|
|
3472
3116
|
reveal: 'Open containing folder',
|
|
3473
3117
|
copy: 'Copy path',
|
|
@@ -3486,6 +3130,57 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3486
3130
|
return typeof value === 'string' ? value.trim() : '';
|
|
3487
3131
|
};
|
|
3488
3132
|
|
|
3133
|
+
/**
|
|
3134
|
+
* The workspace root this session runs in 鈥?what turns a tool row's relative
|
|
3135
|
+
* text back into a path the OS can open. Read defensively, and never guess:
|
|
3136
|
+
* an unresolvable root leaves the menu without copy/reveal for that surface.
|
|
3137
|
+
*/
|
|
3138
|
+
const rootForSession = () => {
|
|
3139
|
+
try {
|
|
3140
|
+
const workspaces = ctx.get('workspaces');
|
|
3141
|
+
if (workspaces === undefined || workspaces === null) return '';
|
|
3142
|
+
// The service face exposes methods only; its snapshot store is not part
|
|
3143
|
+
// of that contract, so read it defensively and fall through to ''.
|
|
3144
|
+
const store = workspaces.list;
|
|
3145
|
+
if (store === undefined || store === null || typeof store.getSnapshot !== 'function') return '';
|
|
3146
|
+
const snapshot = store.getSnapshot();
|
|
3147
|
+
const items = snapshot === undefined || snapshot === null ? undefined : snapshot.items;
|
|
3148
|
+
if (!Array.isArray(items) || items.length === 0) return '';
|
|
3149
|
+
let sessionId = '';
|
|
3150
|
+
const sessions = ctx.get('sessions');
|
|
3151
|
+
if (sessions !== undefined && sessions !== null && typeof sessions.bindingForActive === 'function') {
|
|
3152
|
+
const binding = sessions.bindingForActive();
|
|
3153
|
+
if (binding !== undefined && binding !== null && typeof binding.sessionId === 'string') sessionId = binding.sessionId;
|
|
3154
|
+
}
|
|
3155
|
+
const owner = sessionId === '' ? undefined : items.find((item) => Array.isArray(item.sessionIds) && item.sessionIds.indexOf(sessionId) >= 0);
|
|
3156
|
+
const chosen = owner === undefined ? items[0] : owner;
|
|
3157
|
+
const root = chosen === undefined || chosen === null ? '' : chosen.path;
|
|
3158
|
+
return typeof root === 'string' ? root.replace(/[\\/]+$/, '') : '';
|
|
3159
|
+
} catch (error) {
|
|
3160
|
+
return '';
|
|
3161
|
+
}
|
|
3162
|
+
};
|
|
3163
|
+
const isPathLike = (value) => {
|
|
3164
|
+
if (typeof value !== 'string') return false;
|
|
3165
|
+
const trimmed = value.trim();
|
|
3166
|
+
if (trimmed === '') return false;
|
|
3167
|
+
if (/^[A-Za-z]:[\\/]/.test(trimmed) || trimmed.charAt(0) === '/') return true;
|
|
3168
|
+
// A printed relative path has a separator; a package spec does not.
|
|
3169
|
+
if (trimmed.charAt(0) === '@' || trimmed.indexOf('://') >= 0) return false;
|
|
3170
|
+
return trimmed.indexOf('\\') >= 0 || trimmed.indexOf('/') >= 0;
|
|
3171
|
+
};
|
|
3172
|
+
/** Absolute path for a surface, or '' when nothing trustworthy is available. */
|
|
3173
|
+
const absoluteOf = (value) => {
|
|
3174
|
+
const trimmed = typeof value === 'string' ? value.trim() : '';
|
|
3175
|
+
if (trimmed === '') return '';
|
|
3176
|
+
if (/^[A-Za-z]:[\\/]/.test(trimmed) || trimmed.charAt(0) === '/') return trimmed;
|
|
3177
|
+
if (!isPathLike(trimmed)) return '';
|
|
3178
|
+
const root = rootForSession();
|
|
3179
|
+
if (root === '') return '';
|
|
3180
|
+
const separator = root.indexOf('\\') >= 0 ? '\\' : '/';
|
|
3181
|
+
return root + separator + trimmed.replace(/^[\\/]+/, '');
|
|
3182
|
+
};
|
|
3183
|
+
|
|
3489
3184
|
/**
|
|
3490
3185
|
* Resolve a click target to the file it represents. A changed-file chip
|
|
3491
3186
|
* is both the anchor and the button whose own click opens the product's
|
|
@@ -3501,12 +3196,20 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3501
3196
|
return path === '' ? null : { path: path, button: chip, produced: true };
|
|
3502
3197
|
}
|
|
3503
3198
|
const card = target.closest(CARD);
|
|
3504
|
-
if (card
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3199
|
+
if (card !== null) {
|
|
3200
|
+
const overlay = card.querySelector('button[title]');
|
|
3201
|
+
if (overlay === null) return null;
|
|
3202
|
+
const overlayTitle = overlay.getAttribute('title');
|
|
3203
|
+
const cardPath = absoluteOf(overlayTitle);
|
|
3204
|
+
return cardPath === '' ? null : { path: cardPath, button: overlay, produced: false, previewable: true, replayButton: overlay };
|
|
3205
|
+
}
|
|
3206
|
+
// 行内 `@file` 引用不在这里处理:0.2.0 起交还官方(点击即官方预览器)。
|
|
3207
|
+
// Tool-row file link. Its text is relative to the session's workspace root,
|
|
3208
|
+
// and the product's workspace service does not hand that root over to a
|
|
3209
|
+
// plugin, so there is nothing trustworthy to turn it into a path here. The
|
|
3210
|
+
// product's own click already opens the right file, so this surface is left
|
|
3211
|
+
// entirely alone rather than given a menu whose path items would be wrong.
|
|
3212
|
+
return null;
|
|
3510
3213
|
};
|
|
3511
3214
|
|
|
3512
3215
|
/**
|
|
@@ -3518,7 +3221,8 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3518
3221
|
const preview = (surface) => {
|
|
3519
3222
|
replaying = true;
|
|
3520
3223
|
try {
|
|
3521
|
-
surface.button.
|
|
3224
|
+
const target = surface.replayButton === undefined || surface.replayButton === null ? surface.button : surface.replayButton;
|
|
3225
|
+
target.click();
|
|
3522
3226
|
} finally {
|
|
3523
3227
|
replaying = false;
|
|
3524
3228
|
}
|
|
@@ -3553,9 +3257,50 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3553
3257
|
}
|
|
3554
3258
|
return Promise.resolve(clipboard.writeText(path)).then(() => true).catch(() => false);
|
|
3555
3259
|
};
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3260
|
+
/**
|
|
3261
|
+
* 打开文件 / 在文件管理器中显示,**优先走官方远程**。
|
|
3262
|
+
*
|
|
3263
|
+
* 0.2.0 的 dsh-api-session-controller 提供了这条通路(从宿主端的类型定义里抄下来的形状):
|
|
3264
|
+
* session.canOpenWorkspacePath() → boolean
|
|
3265
|
+
* session.openWorkspacePath({ path }) → 用默认应用打开
|
|
3266
|
+
* session.openWorkspacePath({ path, action: 'reveal' }) → 在文件管理器中显示
|
|
3267
|
+
* session.openWorkspacePath({ path, application }) → 用指定应用打开
|
|
3268
|
+
* 返回 { opened: true }。宿主侧会自己校验路径、解析工作区映射,
|
|
3269
|
+
* 拿不到已验证的映射时抛 RemoteError —— 比我们自己拼路径可靠。
|
|
3270
|
+
*
|
|
3271
|
+
* 我们自己的私有通道(dshome/file.open、dshome/file.reveal)只在官方这条不存在时兜底:
|
|
3272
|
+
* 旧内核没有这个远程。兜底是有代价的——那是本插件自己的实现,产品换做法时它不会跟着变,
|
|
3273
|
+
* 所以它永远排在官方后面,而不是并列。
|
|
3274
|
+
*
|
|
3275
|
+
* 返回 null 表示"官方这条不可用",交给调用方走兜底。
|
|
3276
|
+
*/
|
|
3277
|
+
const officialOpen = (path, reveal) => {
|
|
3278
|
+
let session;
|
|
3279
|
+
try {
|
|
3280
|
+
const connection = ctx.get('connection');
|
|
3281
|
+
session = connection === undefined || connection === null ? undefined : connection.api?.session;
|
|
3282
|
+
} catch (error) {
|
|
3283
|
+
session = undefined;
|
|
3284
|
+
}
|
|
3285
|
+
if (session === undefined || session === null || typeof session.openWorkspacePath !== 'function') return null;
|
|
3286
|
+
let call;
|
|
3287
|
+
try {
|
|
3288
|
+
call = session.openWorkspacePath(reveal ? { path: path, action: 'reveal' } : { path: path });
|
|
3289
|
+
} catch (error) {
|
|
3290
|
+
// 同步抛出(参数被拒、通道没接上)——当成"官方这条不可用",别把异常丢给用户。
|
|
3291
|
+
return null;
|
|
3292
|
+
}
|
|
3293
|
+
return Promise.resolve(call)
|
|
3294
|
+
.then((result) => result !== undefined && result !== null && result.opened === true)
|
|
3295
|
+
.catch(() => false);
|
|
3296
|
+
};
|
|
3297
|
+
const invoke = (endpoint, path, reveal) => {
|
|
3298
|
+
const official = officialOpen(path, reveal === true);
|
|
3299
|
+
if (official !== null) return official;
|
|
3300
|
+
return Promise.resolve(bridgeCall(endpoint, { path: path }))
|
|
3301
|
+
.then((result) => result !== undefined && result !== null && result.ok === true)
|
|
3302
|
+
.catch(() => false);
|
|
3303
|
+
};
|
|
3559
3304
|
|
|
3560
3305
|
const buildItem = (icon, label, onPick) => {
|
|
3561
3306
|
const item = document.createElement('button');
|
|
@@ -3586,21 +3331,35 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3586
3331
|
const node = document.createElement('div');
|
|
3587
3332
|
node.className = CLS;
|
|
3588
3333
|
node.setAttribute('role', 'menu');
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
node.appendChild(buildItem(
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
const separator = document.createElement('div');
|
|
3599
|
-
separator.className = CLS + '-sep';
|
|
3600
|
-
node.appendChild(separator);
|
|
3601
|
-
node.appendChild(buildItem(ICON_PREVIEW, surface.produced ? text.previewProduced : text.preview, () => {
|
|
3602
|
-
preview(surface);
|
|
3334
|
+
// 鎵撳紑鏂囦欢 first; when the surface owns that action (the product's viewer)
|
|
3335
|
+
// the item IS that action, replayed for real instead of a second route.
|
|
3336
|
+
const opensItself = surface.replayButton !== undefined && surface.replayButton !== null;
|
|
3337
|
+
node.appendChild(buildItem(ICON_OPEN, opensItself ? text.openOfficial : text.open, () => {
|
|
3338
|
+
if (opensItself) {
|
|
3339
|
+
preview(surface);
|
|
3340
|
+
return;
|
|
3341
|
+
}
|
|
3342
|
+
invoke('dshome/file.open', path, false).then((ok) => { if (!ok) toast(text.failed); });
|
|
3603
3343
|
}));
|
|
3344
|
+
// The two path items need a real path. A surface the product only labels
|
|
3345
|
+
// with relative text keeps the viewer item alone rather than offering an
|
|
3346
|
+
// action that would point at the wrong file.
|
|
3347
|
+
if (path !== '') {
|
|
3348
|
+
node.appendChild(buildItem(ICON_REVEAL, text.reveal, () => {
|
|
3349
|
+
invoke('dshome/file.reveal', path, true).then((ok) => { if (!ok) toast(text.failed); });
|
|
3350
|
+
}));
|
|
3351
|
+
node.appendChild(buildItem(ICON_COPY, text.copy, () => {
|
|
3352
|
+
copyPath(path).then((ok) => toast(ok ? text.copied : text.copyDenied));
|
|
3353
|
+
}));
|
|
3354
|
+
}
|
|
3355
|
+
if (surface.previewable === true) {
|
|
3356
|
+
const separator = document.createElement('div');
|
|
3357
|
+
separator.className = CLS + '-sep';
|
|
3358
|
+
node.appendChild(separator);
|
|
3359
|
+
node.appendChild(buildItem(ICON_PREVIEW, surface.produced ? text.previewProduced : text.preview, () => {
|
|
3360
|
+
preview(surface);
|
|
3361
|
+
}));
|
|
3362
|
+
}
|
|
3604
3363
|
document.body.appendChild(node);
|
|
3605
3364
|
// Clamp to the viewport so a card near an edge never opens a menu
|
|
3606
3365
|
// the user cannot reach.
|
|
@@ -3627,8 +3386,14 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3627
3386
|
const onClickCapture = (event) => {
|
|
3628
3387
|
if (replaying || event.defaultPrevented) return;
|
|
3629
3388
|
if (event.button !== undefined && event.button !== 0) return;
|
|
3389
|
+
// 左键只在「本轮文件改动」的 chip 上接管:那类元素自己没有任何可见动作
|
|
3390
|
+
// (点下去像没反应),所以左键直接开菜单是净收益。交付卡片有自己的左键
|
|
3391
|
+
// 预览,保持原样、只在右键开菜单。行内 @file 引用已交还官方。
|
|
3392
|
+
if (event.detail > 1) return;
|
|
3393
|
+
const selection = window.getSelection();
|
|
3394
|
+
if (selection !== null && selection.isCollapsed === false) return;
|
|
3630
3395
|
const surface = surfaceOf(event.target);
|
|
3631
|
-
if (surface === null ||
|
|
3396
|
+
if (surface === null || surface.produced !== true) return;
|
|
3632
3397
|
event.preventDefault();
|
|
3633
3398
|
event.stopPropagation();
|
|
3634
3399
|
openMenu(surface, event.clientX, event.clientY);
|
|
@@ -3845,6 +3610,78 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
3845
3610
|
panel.appendChild(listEl);
|
|
3846
3611
|
document.body.appendChild(panel);
|
|
3847
3612
|
|
|
3613
|
+
// 拖动(0.2.0 新增需求):默认仍锚在右下角,一旦拖动就切换到 left/top
|
|
3614
|
+
// 定位并把位置记进 localStorage,下次打开还在原地。用 Pointer Events +
|
|
3615
|
+
// setPointerCapture,拖出面板外也不会丢事件;双击标题栏复位到默认角落。
|
|
3616
|
+
const PANEL_POS_KEY = 'dshome.subagent.panel.position';
|
|
3617
|
+
const clampToViewport = (left, top) => {
|
|
3618
|
+
const box = panel.getBoundingClientRect();
|
|
3619
|
+
const maxLeft = Math.max(8, window.innerWidth - box.width - 8);
|
|
3620
|
+
const maxTop = Math.max(8, window.innerHeight - box.height - 8);
|
|
3621
|
+
return { left: Math.min(Math.max(8, left), maxLeft), top: Math.min(Math.max(8, top), maxTop) };
|
|
3622
|
+
};
|
|
3623
|
+
const placePanel = (left, top) => {
|
|
3624
|
+
const at = clampToViewport(left, top);
|
|
3625
|
+
panel.style.left = at.left + 'px';
|
|
3626
|
+
panel.style.top = at.top + 'px';
|
|
3627
|
+
panel.style.right = 'auto';
|
|
3628
|
+
panel.style.bottom = 'auto';
|
|
3629
|
+
};
|
|
3630
|
+
const savedPosition = () => {
|
|
3631
|
+
try {
|
|
3632
|
+
const raw = window.localStorage.getItem(PANEL_POS_KEY);
|
|
3633
|
+
if (raw === null) return null;
|
|
3634
|
+
const parsed = JSON.parse(raw);
|
|
3635
|
+
return typeof parsed?.left === 'number' && typeof parsed?.top === 'number' ? parsed : null;
|
|
3636
|
+
} catch (error) { return null; }
|
|
3637
|
+
};
|
|
3638
|
+
const persistPosition = () => {
|
|
3639
|
+
try {
|
|
3640
|
+
const box = panel.getBoundingClientRect();
|
|
3641
|
+
window.localStorage.setItem(PANEL_POS_KEY, JSON.stringify({ left: Math.round(box.left), top: Math.round(box.top) }));
|
|
3642
|
+
} catch (error) { /* 存不下就算了,不影响本次会话 */ }
|
|
3643
|
+
};
|
|
3644
|
+
head.classList.add('dshome-sa-grab');
|
|
3645
|
+
head.addEventListener('pointerdown', (event) => {
|
|
3646
|
+
if (event.button !== undefined && event.button !== 0) return;
|
|
3647
|
+
// 标题栏上的折叠按钮不参与拖动。
|
|
3648
|
+
if (event.target instanceof Node && collapseBtn.contains(event.target)) return;
|
|
3649
|
+
const box = panel.getBoundingClientRect();
|
|
3650
|
+
const offsetX = event.clientX - box.left;
|
|
3651
|
+
const offsetY = event.clientY - box.top;
|
|
3652
|
+
head.setPointerCapture(event.pointerId);
|
|
3653
|
+
panel.classList.add('dshome-sa-dragging');
|
|
3654
|
+
event.preventDefault();
|
|
3655
|
+
const move = (moveEvent) => placePanel(moveEvent.clientX - offsetX, moveEvent.clientY - offsetY);
|
|
3656
|
+
const up = () => {
|
|
3657
|
+
head.removeEventListener('pointermove', move);
|
|
3658
|
+
head.removeEventListener('pointerup', up);
|
|
3659
|
+
head.removeEventListener('pointercancel', up);
|
|
3660
|
+
panel.classList.remove('dshome-sa-dragging');
|
|
3661
|
+
try { head.releasePointerCapture(event.pointerId); } catch (error) { /* 已释放 */ }
|
|
3662
|
+
persistPosition();
|
|
3663
|
+
};
|
|
3664
|
+
head.addEventListener('pointermove', move);
|
|
3665
|
+
head.addEventListener('pointerup', up);
|
|
3666
|
+
head.addEventListener('pointercancel', up);
|
|
3667
|
+
});
|
|
3668
|
+
head.addEventListener('dblclick', (event) => {
|
|
3669
|
+
if (event.target instanceof Node && collapseBtn.contains(event.target)) return;
|
|
3670
|
+
panel.style.left = '';
|
|
3671
|
+
panel.style.top = '';
|
|
3672
|
+
panel.style.right = '';
|
|
3673
|
+
panel.style.bottom = '';
|
|
3674
|
+
try { window.localStorage.removeItem(PANEL_POS_KEY); } catch (error) { /* 忽略 */ }
|
|
3675
|
+
});
|
|
3676
|
+
window.addEventListener('resize', () => {
|
|
3677
|
+
const saved = savedPosition();
|
|
3678
|
+
if (saved !== null) placePanel(saved.left, saved.top);
|
|
3679
|
+
});
|
|
3680
|
+
{
|
|
3681
|
+
const saved = savedPosition();
|
|
3682
|
+
if (saved !== null) placePanel(saved.left, saved.top);
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3848
3685
|
const timingOf = (s) => (
|
|
3849
3686
|
s.projectionValues === undefined || s.projectionValues === null
|
|
3850
3687
|
? undefined
|
|
@@ -4058,6 +3895,25 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4058
3895
|
row.sweepEl.style.width = '0%';
|
|
4059
3896
|
};
|
|
4060
3897
|
|
|
3898
|
+
/**
|
|
3899
|
+
* 中断失败要看得见。旧版失败时只是把乐观状态退回去,屏幕上什么都没留下,
|
|
3900
|
+
* 用户看到的就是"按了没用"。这里除了退回状态,还把原因写到控制台与按钮的
|
|
3901
|
+
* 悬停提示上,并尽量写进这一行的说明文字(下一秒会被计时刷新覆盖,属于尽力而为)。
|
|
3902
|
+
*/
|
|
3903
|
+
const reportStopFailure = (row, btn, error) => {
|
|
3904
|
+
rollbackStop(row, btn);
|
|
3905
|
+
const text = error === undefined || error === null
|
|
3906
|
+
? '主机没有接受这次中断'
|
|
3907
|
+
: (typeof error === 'string' ? error : (error.message === undefined ? String(error) : error.message));
|
|
3908
|
+
try { console.warn('[dsh-deepseek-style-theme] 中断子代理失败:', error); } catch (ignored) { /* 忽略 */ }
|
|
3909
|
+
if (btn !== undefined && btn !== null) {
|
|
3910
|
+
try { btn.title = '中断失败:' + text; } catch (ignored) { /* 忽略 */ }
|
|
3911
|
+
}
|
|
3912
|
+
if (row !== undefined && row.metaEl !== undefined && row.metaEl !== null) {
|
|
3913
|
+
row.metaEl.textContent = '中断失败:' + text;
|
|
3914
|
+
}
|
|
3915
|
+
};
|
|
3916
|
+
|
|
4061
3917
|
const beginCompleting = (row) => {
|
|
4062
3918
|
row.phase = 'completing';
|
|
4063
3919
|
row.completeStart = performance.now();
|
|
@@ -4066,17 +3922,30 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4066
3922
|
};
|
|
4067
3923
|
|
|
4068
3924
|
/**
|
|
4069
|
-
*
|
|
4070
|
-
*
|
|
4071
|
-
*
|
|
4072
|
-
*
|
|
3925
|
+
* 子代理的 parent/child 地址。
|
|
3926
|
+
*
|
|
3927
|
+
* 0.2.0 起**不要再自己拼**:会话摘要里已经没有 `projectionValues.subagent`
|
|
3928
|
+
* 与 `parentId`(这正是旧版红按钮失灵的根因——地址取不到,
|
|
3929
|
+
* canInterrupt 为假,按钮只闪一下红就结束)。官方把这件事收进了
|
|
3930
|
+
* `ctx.sessions.subagentAddress(childSessionId)`,它内部会在各父级的
|
|
3931
|
+
* `subagentCatalog` 里查这条子会话,拿到权威的 { parentSessionId,
|
|
3932
|
+
* childSessionId, mode }。这里优先用它,其余为旧版兜底。
|
|
4073
3933
|
*/
|
|
4074
3934
|
const addressOf = (s) => {
|
|
3935
|
+
try {
|
|
3936
|
+
if (typeof sessions.subagentAddress === 'function') {
|
|
3937
|
+
const address = sessions.subagentAddress(s.id);
|
|
3938
|
+
if (address !== undefined && address !== null && typeof address.parentSessionId === 'string') return address;
|
|
3939
|
+
}
|
|
3940
|
+
} catch (error) { /* 落回下面的兜底 */ }
|
|
3941
|
+
const parentId = typeof s.parentId === 'string' && s.parentId !== ''
|
|
3942
|
+
? s.parentId
|
|
3943
|
+
: (typeof s.parentSessionId === 'string' && s.parentSessionId !== '' ? s.parentSessionId : '');
|
|
3944
|
+
if (parentId === '') return undefined;
|
|
4075
3945
|
const identity = s.projectionValues === undefined || s.projectionValues === null ? undefined : s.projectionValues.subagent;
|
|
4076
3946
|
let mode = 'continuable';
|
|
4077
3947
|
if (identity !== undefined && identity !== null && (identity.mode === 'one-shot' || identity.mode === 'continuable')) mode = identity.mode;
|
|
4078
|
-
|
|
4079
|
-
return { parentSessionId: s.parentId, childSessionId: s.id, mode };
|
|
3948
|
+
return { parentSessionId: parentId, childSessionId: s.id, mode };
|
|
4080
3949
|
};
|
|
4081
3950
|
|
|
4082
3951
|
const stopSubagent = (s, btn, event) => {
|
|
@@ -4086,18 +3955,31 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4086
3955
|
}
|
|
4087
3956
|
const row = rows.get(s.id);
|
|
4088
3957
|
if (row !== undefined && row.phase !== 'running') return;
|
|
4089
|
-
let face;
|
|
4090
|
-
try {
|
|
4091
|
-
const binding = sessions.binding(s.id);
|
|
4092
|
-
face = binding === undefined || binding === null ? undefined : binding.session;
|
|
4093
|
-
} catch (error) {
|
|
4094
|
-
face = undefined;
|
|
4095
|
-
}
|
|
4096
3958
|
const address = addressOf(s);
|
|
4097
3959
|
const api = connection === undefined || connection === null ? undefined : connection.api;
|
|
4098
|
-
const
|
|
4099
|
-
const
|
|
4100
|
-
|
|
3960
|
+
const subagents = api === undefined || api === null ? undefined : api.subagents;
|
|
3961
|
+
const sessionApi = api === undefined || api === null ? undefined : api.session;
|
|
3962
|
+
// 照官方会话取消的写法(dsh-client-ui-chat 的 cancel):
|
|
3963
|
+
// 有子代理地址 -> subagents.interruptByParent(child, parent, "continuable")
|
|
3964
|
+
// 没有地址 -> session.cancel({ sessionId })
|
|
3965
|
+
// 关键在第三个数是**写死的 "continuable"**,不是这条子代理自己的 mode。
|
|
3966
|
+
// 旧版要求 address.mode === 'continuable' 才肯调用,于是 one-shot 子代理
|
|
3967
|
+
// 永远落进"没权限"分支——按下去只闪一下红,表现就是"点了没用"。
|
|
3968
|
+
const byParent = address !== undefined && subagents !== undefined && subagents !== null
|
|
3969
|
+
&& typeof subagents.interruptByParent === 'function';
|
|
3970
|
+
const bySession = !byParent && typeof s.id === 'string' && s.id !== ''
|
|
3971
|
+
&& sessionApi !== undefined && sessionApi !== null && typeof sessionApi.cancel === 'function';
|
|
3972
|
+
let face;
|
|
3973
|
+
if (!byParent && !bySession) {
|
|
3974
|
+
try {
|
|
3975
|
+
const binding = sessions.binding(s.id);
|
|
3976
|
+
face = binding === undefined || binding === null ? undefined : binding.session;
|
|
3977
|
+
} catch (error) {
|
|
3978
|
+
face = undefined;
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
const byFace = !byParent && !bySession && face !== undefined && face !== null && typeof face.cancel === 'function';
|
|
3982
|
+
if (!byParent && !bySession && !byFace) {
|
|
4101
3983
|
flashDenied(btn);
|
|
4102
3984
|
return;
|
|
4103
3985
|
}
|
|
@@ -4107,18 +3989,20 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4107
3989
|
if (row !== undefined) startStopProgress(row);
|
|
4108
3990
|
let cancelPromise;
|
|
4109
3991
|
try {
|
|
4110
|
-
cancelPromise =
|
|
4111
|
-
? Promise.resolve(
|
|
4112
|
-
:
|
|
3992
|
+
cancelPromise = byParent
|
|
3993
|
+
? Promise.resolve(subagents.interruptByParent(address.childSessionId, address.parentSessionId, 'continuable'))
|
|
3994
|
+
: bySession
|
|
3995
|
+
? Promise.resolve(sessionApi.cancel({ sessionId: s.id }))
|
|
3996
|
+
: Promise.resolve(face.cancel());
|
|
4113
3997
|
} catch (error) {
|
|
4114
|
-
|
|
3998
|
+
reportStopFailure(row, btn, error);
|
|
4115
3999
|
return;
|
|
4116
4000
|
}
|
|
4117
4001
|
cancelPromise.then((result) => {
|
|
4118
4002
|
const ok = result !== undefined && result !== null && result.ok === true;
|
|
4119
|
-
if (!ok)
|
|
4120
|
-
}).catch(() => {
|
|
4121
|
-
|
|
4003
|
+
if (!ok) reportStopFailure(row, btn, result === undefined || result === null ? undefined : result.error);
|
|
4004
|
+
}).catch((error) => {
|
|
4005
|
+
reportStopFailure(row, btn, error);
|
|
4122
4006
|
});
|
|
4123
4007
|
};
|
|
4124
4008
|
|
|
@@ -4319,10 +4203,11 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
4319
4203
|
// set they asked to keep. Ids match the host schema's `backgroundMode`
|
|
4320
4204
|
// enum; the free-text value for 方式3 is `customBackground`.
|
|
4321
4205
|
const DSTT_BACKGROUNDS = ["classic", "white", "custom", "bold"];
|
|
4322
|
-
// `
|
|
4323
|
-
//
|
|
4324
|
-
//
|
|
4325
|
-
|
|
4206
|
+
// `classic` by default: soft lead + pure white + near-white, one colour
|
|
4207
|
+
// family for the buttons and the surfaces alike and no second hue -- the look the
|
|
4208
|
+
// product owner asked to ship. `bold` (the 1.43.12 three-chroma set) stays
|
|
4209
|
+
// selectable in the DSTT tab for anyone who wants it back.
|
|
4210
|
+
const DSTT_BACKGROUND_DEFAULT = "classic";
|
|
4326
4211
|
const DSTT_BACKGROUND_LABELS = {
|
|
4327
4212
|
classic: { zh: "① 主色+纯白+近白", en: "1 · Lead + white + near-white" },
|
|
4328
4213
|
white: { zh: "② 主色+纯白", en: "2 · Lead + white" },
|
|
@@ -5153,6 +5038,45 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
5153
5038
|
// every rule inside is gated on html[data-dshome-glass="liquid"], so the
|
|
5154
5039
|
// frosted pane keeps rendering exactly what it rendered before.
|
|
5155
5040
|
ctx.effect(() => injectStyle(LIQUID_CSS, 'liquid-glass.css'), 'deepseek-style-theme: apple liquid glass');
|
|
5041
|
+
// Popup menus are portaled out of every pane we glass, so they need their own
|
|
5042
|
+
// sheet; see MENU_GLASS_CSS for why it decorates rather than repaints.
|
|
5043
|
+
ctx.effect(() => injectStyle(MENU_GLASS_CSS, 'menu-glass.css'), 'deepseek-style-theme: menu glass');
|
|
5044
|
+
|
|
5045
|
+
// 更新之后"哪条选择器失效了"的自检入口。
|
|
5046
|
+
// 产品每次构建都会重排类哈希,失效是安静的:规则还在、只是永不命中,
|
|
5047
|
+
// 肉眼看就是"某个面的样式没了"。这个函数把每个锚点当天的命中数摆出来,
|
|
5048
|
+
// 于是"静默失效"变成"一眼能看见":控制台里 __dshomeAnchors()。
|
|
5049
|
+
// 锚点表直接从 PATCH_BLOCKS 推导,不另外维护一份,免得两边走散。
|
|
5050
|
+
// 说明:它只查"产品里还有没有这个锚点",不查"样式有没有真的生效"——
|
|
5051
|
+
// 命中数非零不等于样式正确,命中数为 0 则一定没生效。
|
|
5052
|
+
try {
|
|
5053
|
+
window.__dshomeAnchors = () => {
|
|
5054
|
+
const report = {};
|
|
5055
|
+
const probe = (label, selector) => {
|
|
5056
|
+
try {
|
|
5057
|
+
report[label] = document.querySelectorAll(selector).length;
|
|
5058
|
+
} catch (error) {
|
|
5059
|
+
report[label] = '选择器无效';
|
|
5060
|
+
}
|
|
5061
|
+
};
|
|
5062
|
+
for (const block of PATCH_BLOCKS) {
|
|
5063
|
+
for (const part of block.anchor.split(',')) {
|
|
5064
|
+
const selector = part.trim();
|
|
5065
|
+
if (selector !== '') probe(selector, selector);
|
|
5066
|
+
}
|
|
5067
|
+
}
|
|
5068
|
+
probe('用户气泡', USER_BUBBLE);
|
|
5069
|
+
probe('侧栏外壳', SIDEBAR);
|
|
5070
|
+
probe('输入卡卡片', '[data-composer-card]');
|
|
5071
|
+
probe('滚动区', '[data-conversation-scroll]');
|
|
5072
|
+
probe('菜单材质', '[data-menu-material]');
|
|
5073
|
+
probe('路径标签', '[data-path-label]');
|
|
5074
|
+
probe('流体画布', 'canvas[data-dsh-deepseek-canvas]');
|
|
5075
|
+
const dead = Object.keys(report).filter((k) => report[k] === 0);
|
|
5076
|
+
return { 命中数: report, 零命中的锚点: dead };
|
|
5077
|
+
};
|
|
5078
|
+
window.__dshomeBuild = '2.1.90 adapt-0.2.0-rc.2';
|
|
5079
|
+
} catch (error) { /* 页面拒绝写 window 时不影响主题 */ }
|
|
5156
5080
|
|
|
5157
5081
|
if (theme !== undefined) {
|
|
5158
5082
|
const disposeTokens = theme.overrideTokens('deepseek-style-theme', {
|
|
@@ -5168,7 +5092,20 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
5168
5092
|
'--dsw-alias-state-error-primary': { light: '#ef4444', dark: '#ff6b6b' },
|
|
5169
5093
|
'--dsw-alias-state-success-primary': { light: '#22c55e', dark: '#4ed17e' },
|
|
5170
5094
|
'--dsw-alias-state-warn-primary': { light: '#f59e0b', dark: '#f7ad31' },
|
|
5171
|
-
|
|
5095
|
+
// Windows 标题栏右上角那三个按钮(最小化/最大化/关闭)的底色就是这一条:
|
|
5096
|
+
// 桌面外壳在渲染进程里放了一个 span,它的 background-color 写成
|
|
5097
|
+
// var(--dsw-specific-sidebar-fill),每帧量出来发给主进程
|
|
5098
|
+
// setTitleBarOverlay 当窗口控件条的颜色(图标色取 --dsw-alias-label-primary)。
|
|
5099
|
+
// 所以这三个按钮我们改不了它们本身,但**能改这一条 token**。
|
|
5100
|
+
// 原来这里是 rgba(255,255,255,0.5):半透明白压在窗口的亚克力底上,
|
|
5101
|
+
// 量出来是 #f9fafe;而旁边的标题栏是半透明底压在流动背景上,量出来 #eaeffa——
|
|
5102
|
+
// 两边差出一圈,就是用户看到的「按钮和标题栏有界线」。
|
|
5103
|
+
// 改成**不透明**的浅蓝(照着旁边 #f1f4fe 取),两边同色,界线消失。
|
|
5104
|
+
// 注意必须是不透明:半透明的话窗口底和页面底不同,又会分家;
|
|
5105
|
+
// 也必须是普通颜色,不能写 color-mix —— 外壳用 canvas fillStyle 解析它,
|
|
5106
|
+
// canvas 不认 color-mix,会静默退回上一个值。
|
|
5107
|
+
// 代价:标题栏最上面那条 40px 不再透出流动背景,变成一块平色。
|
|
5108
|
+
'--dsw-specific-sidebar-fill': { light: '#eef2fd', dark: '#0b1730' }
|
|
5172
5109
|
});
|
|
5173
5110
|
ctx.effect(() => disposeTokens);
|
|
5174
5111
|
}
|
|
@@ -5244,11 +5181,7 @@ body[data-dshome-dark] .${CLS}{background:rgba(14,30,60,.94);box-shadow:0 18px 4
|
|
|
5244
5181
|
try { return watchBrandLink(); } catch (error) { return () => {}; }
|
|
5245
5182
|
});
|
|
5246
5183
|
ctx.effect(() => {
|
|
5247
|
-
try { return watchHeaderInteraction(); } catch (error) { return () => {}; }
|
|
5248
5184
|
});
|
|
5249
|
-
ctx.effect(() => {
|
|
5250
|
-
try { return watchWorkspaceMenu(ctx); } catch (error) { return () => {}; }
|
|
5251
|
-
}, 'deepseek-style-theme: workspace open menu');
|
|
5252
5185
|
ctx.effect(() => {
|
|
5253
5186
|
try { return watchFileCardMenu(ctx); } catch (error) { return () => {}; }
|
|
5254
5187
|
}, 'deepseek-style-theme: delivered file card menu');
|
|
@@ -5307,3 +5240,4 @@ try {
|
|
|
5307
5240
|
try {
|
|
5308
5241
|
window.__ModuleLoader__.load({ id: "@dsh-external/dsh-deepseek-style-theme", factory: dshDeepseekStyleThemeFactory });
|
|
5309
5242
|
} catch (error) {}
|
|
5243
|
+
})();
|