dsh-long-plugins 2.5.0 → 2.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/client/client.js +7 -800
- package/client/vendor/chart.umd.min.js +0 -0
- package/client/vendor/docx-preview.min.js +0 -0
- package/client/vendor/jszip.min.js +0 -0
- package/client/vendor/pptxviewjs.min.js +0 -0
- package/dsh.plugin.json +1 -1
- package/lib/index.js +57 -238
- package/package.json +2 -2
- package/skill/dsh-common-plugins-install/SKILL.md +0 -0
- package/skill/dsh-web-start-panel-install/SKILL.md +0 -0
- package/skill/dsh-web-win-service-install/SKILL.md +0 -0
package/client/client.js
CHANGED
|
@@ -2556,10 +2556,10 @@ window.__ModuleLoader__.load({
|
|
|
2556
2556
|
let scrollAnim = 0
|
|
2557
2557
|
let scrollTarget = -1
|
|
2558
2558
|
let scrollFrom = 0
|
|
2559
|
-
//
|
|
2559
|
+
// 会话导航开关:从模块配置读取 turnRuler;false=off 隐藏刻尺(避免与 DSH 原生回合导航冲突)
|
|
2560
2560
|
;(() => {
|
|
2561
2561
|
const setVis = (en) => { try { document.documentElement.setAttribute('data-dsh-turnruler', en ? 'on' : 'off') } catch (_) {} }
|
|
2562
|
-
fetch('/api/dsh-uploads/
|
|
2562
|
+
fetch('/api/dsh-uploads/modules-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => { if (b && b.cfg) setVis((b.cfg.modules && b.cfg.modules.turnRuler) !== false) }).catch(() => {})
|
|
2563
2563
|
})()
|
|
2564
2564
|
|
|
2565
2565
|
const ensureRuler = () => {
|
|
@@ -3132,241 +3132,8 @@ window.__ModuleLoader__.load({
|
|
|
3132
3132
|
},
|
|
3133
3133
|
}
|
|
3134
3134
|
|
|
3135
|
-
// ===== dsh-long-plugins:
|
|
3136
|
-
//
|
|
3137
|
-
// 会话窗口(centerCol)做成磨砂玻璃悬浮卡片(backdrop-filter blur + 半透明背景)。
|
|
3138
|
-
const GLASS_CSS = `
|
|
3139
|
-
html[data-dsh-glass="on"] body { position: relative; }
|
|
3140
|
-
/* 方案A:背景图铺满整页(含左栏/顶栏/底部,全透明透出);仅会话滚动区做磨砂玻璃卡片。 */
|
|
3141
|
-
/* 左栏内部容器(或其它用此变量的)也透明,让背景图透到左栏。只覆盖左栏专用变量,避免影响设置面板。 */
|
|
3142
|
-
html[data-dsh-glass="on"] body {
|
|
3143
|
-
--dsw-specific-sidebar-fill: transparent !important;
|
|
3144
|
-
}
|
|
3145
|
-
/* 左栏 / 顶栏 / 底部:完全透明,让 frame 背景图连成一张透出(不做罩/不做磨砂) */
|
|
3146
|
-
html[data-dsh-glass="on"] #root [class*="sidebarCol"],
|
|
3147
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_header"],
|
|
3148
|
-
html[data-dsh-glass="on"] #root [class*="centerCol"],
|
|
3149
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_root"] {
|
|
3150
|
-
background-color: transparent !important;
|
|
3151
|
-
backdrop-filter: none;
|
|
3152
|
-
-webkit-backdrop-filter: none;
|
|
3153
|
-
}
|
|
3154
|
-
/* 左栏顶部「新会话」按钮:去掉白/黑实底与边框,透出背景图;文字颜色交由下方统一的侧边栏/顶栏可读文字规则决定 */
|
|
3155
|
-
html[data-dsh-glass="on"] #root [class*="newSession"] {
|
|
3156
|
-
background-color: transparent !important;
|
|
3157
|
-
background-image: none !important;
|
|
3158
|
-
border: none !important;
|
|
3159
|
-
border-radius: 0 !important;
|
|
3160
|
-
box-shadow: none !important;
|
|
3161
|
-
backdrop-filter: none !important;
|
|
3162
|
-
-webkit-backdrop-filter: none !important;
|
|
3163
|
-
}
|
|
3164
|
-
/* 左栏/顶栏文字可读性改用 JS 处理(见 glassPlugin.apply 的 applyChromeText),
|
|
3165
|
-
只作用于主界面左栏/顶栏、并跳过设置/弹窗/浮层,避免波及设置面板 */
|
|
3166
|
-
/* 左栏内部根 / 底部容器也用透明(这些是 DSH 当前版本哈希类名;背景图要透到左栏与底部) */
|
|
3167
|
-
html[data-dsh-glass="on"] #root [class*="hHd-Xa_root"],
|
|
3168
|
-
html[data-dsh-glass="on"] #root [class*="FJxK0a_root"] {
|
|
3169
|
-
background-color: transparent !important;
|
|
3170
|
-
}
|
|
3171
|
-
/* 会话滚动区:去掉独立磨砂卡片,直接透出共享背景(背景图+罩层颜色+模糊) */
|
|
3172
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] {
|
|
3173
|
-
background-color: transparent !important;
|
|
3174
|
-
background-image: none !important;
|
|
3175
|
-
backdrop-filter: none !important;
|
|
3176
|
-
-webkit-backdrop-filter: none !important;
|
|
3177
|
-
border-radius: 0 !important;
|
|
3178
|
-
box-shadow: none !important;
|
|
3179
|
-
overflow: auto;
|
|
3180
|
-
width: 100% !important;
|
|
3181
|
-
max-width: none !important;
|
|
3182
|
-
margin: 0 !important;
|
|
3183
|
-
}
|
|
3184
|
-
/* 背景图独立层:铺满整页、位于内容之下,可单独设背景图模糊与透明度,不影响内容清晰度 */
|
|
3185
|
-
.dsh-glass-bgimg{position:fixed;inset:0;z-index:-1;pointer-events:none;background-size:cover;background-position:center;background-repeat:no-repeat;display:none;}
|
|
3186
|
-
/* 滚动条:细、半透明、圆角,且左移贴在内容区(不占卡片最右边缘),让右边缘过渡柔和 */
|
|
3187
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar { width: 8px; }
|
|
3188
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-track { background: transparent; }
|
|
3189
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.35); border-radius: 8px; }
|
|
3190
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.5); }
|
|
3191
|
-
/* 会话区禁用玻璃:scrollBody 还原为透明、无磨砂、无圆角/阴影(保持 DSH 原生会话区外观) */
|
|
3192
|
-
html[data-dsh-glass="on"][data-dsh-session-glass="off"] #root [class*="wSkVaW_scrollBody"] {
|
|
3193
|
-
background-color: transparent !important;
|
|
3194
|
-
background-image: none !important;
|
|
3195
|
-
backdrop-filter: none !important;
|
|
3196
|
-
-webkit-backdrop-filter: none !important;
|
|
3197
|
-
border-radius: 0 !important;
|
|
3198
|
-
box-shadow: none !important;
|
|
3199
|
-
}
|
|
3200
|
-
/* 输入区外围(composerSeat/uV2eYG_root)透明,白块去掉、透出背景 */
|
|
3201
|
-
html[data-dsh-glass="on"] [class*="wSkVaW_composerSeat"],
|
|
3202
|
-
html[data-dsh-glass="on"] [class*="uV2eYG_root"] {
|
|
3203
|
-
background-color: transparent !important;
|
|
3204
|
-
background-image: none !important;
|
|
3205
|
-
box-shadow: none !important;
|
|
3206
|
-
backdrop-filter: none !important;
|
|
3207
|
-
-webkit-backdrop-filter: none !important;
|
|
3208
|
-
}
|
|
3209
|
-
/* 输入框盒子(uV2eYG_card):实底(浅色=白/深色=深)并在背景图之上,文字随主题,保证可读。
|
|
3210
|
-
加 #root 提高优先级,压过 DSH 自身把 card 背景设透明的规则(与 v2.2.1 一致) */
|
|
3211
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"],
|
|
3212
|
-
html[data-dsh-glass="on"] [class*="uV2eYG_card"] {
|
|
3213
|
-
position: relative !important;
|
|
3214
|
-
z-index: 0 !important;
|
|
3215
|
-
background-color: #ffffff !important;
|
|
3216
|
-
background-image: none !important;
|
|
3217
|
-
color: #1a2332 !important;
|
|
3218
|
-
box-shadow: none !important;
|
|
3219
|
-
backdrop-filter: none !important;
|
|
3220
|
-
-webkit-backdrop-filter: none !important;
|
|
3221
|
-
}
|
|
3222
|
-
@media (prefers-color-scheme: dark) {
|
|
3223
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"],
|
|
3224
|
-
html[data-dsh-glass="on"] [class*="uV2eYG_card"] { background: #1c2530 !important; color: #f5f7fa !important; }
|
|
3225
|
-
}
|
|
3226
|
-
/* 会话语流"耗时统计"(turn-tail: 12:38/用时/首token/tok/s):白字+深投影,避免淡字叠在背景图上看不见 */
|
|
3227
|
-
html[data-dsh-glass="on"] [class*="osXY9a_root"],
|
|
3228
|
-
html[data-dsh-glass="on"] [class*="osXY9a_root"] span,
|
|
3229
|
-
html[data-dsh-glass="on"] [class*="p-xYUq_timeEnd"],
|
|
3230
|
-
html[data-dsh-glass="on"] [class*="p-xYUq_"] span {
|
|
3231
|
-
color: #ffffff !important;
|
|
3232
|
-
text-shadow: 0 1px 1px rgba(0,0,0,.5);
|
|
3233
|
-
}
|
|
3234
|
-
/* (移除 color/text-shadow 过渡: 会在收尾重渲染时短暂贴默认色导致"闪黑") */
|
|
3235
|
-
/* DSH 弹窗/浮层(提问/确认/重命名等):给不透明底+主题文字, 杜绝透明与会话内容重叠 */
|
|
3236
|
-
html[data-dsh-glass="on"] [class*="Radix"] [class*="Content"],
|
|
3237
|
-
html[data-dsh-glass="on"] [class*="DialogContent"],
|
|
3238
|
-
html[data-dsh-glass="on"] [class*="ModalContent"],
|
|
3239
|
-
html[data-dsh-glass="on"] [class*="overlay"] [class*="content"],
|
|
3240
|
-
html[data-dsh-glass="on"] [class*="_content"][class*="dialog"] {
|
|
3241
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
|
|
3242
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3243
|
-
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3244
|
-
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3245
|
-
}
|
|
3246
|
-
/* DSH 提问/弹窗卡(M8wy4a_card):不透明底+原生文字, 避免透明重叠、白字。
|
|
3247
|
-
注意: 不能再用 [data-chain-overlay-fallback] [class*=card](会误匹配输入框 uV2eYG_card 加 blur 造成重影) */
|
|
3248
|
-
html[data-dsh-glass="on"] [class*="M8wy4a_card"] {
|
|
3249
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
|
|
3250
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3251
|
-
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3252
|
-
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3253
|
-
}
|
|
3254
|
-
/* 任务/todo 面板(lXshSW_root, 在 conversation.input.dock 内):半透明磨砂底+可读文字 */
|
|
3255
|
-
html[data-dsh-glass="on"] [class*="lXshSW_root"],
|
|
3256
|
-
html[data-dsh-glass="on"] [class*="lXshSW_body"] {
|
|
3257
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 62%, transparent) !important;
|
|
3258
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3259
|
-
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3260
|
-
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3261
|
-
}
|
|
3262
|
-
html[data-dsh-glass="on"] [class*="lXshSW_header"] [class*="lXshSW_title"],
|
|
3263
|
-
html[data-dsh-glass="on"] [class*="lXshSW_header"] [class*="lXshSW_progress"],
|
|
3264
|
-
html[data-dsh-glass="on"] [class*="lXshSW_item"] {
|
|
3265
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3266
|
-
}
|
|
3267
|
-
/* 排队消息/等待任务面板(_7yHdaG_dock/panel, 在 conversation.input.dock 内):不透明/磨砂底+可读文字 */
|
|
3268
|
-
html[data-dsh-glass="on"] [class*="_7yHdaG_panel"],
|
|
3269
|
-
html[data-dsh-glass="on"] [class*="_7yHdaG_dock"] {
|
|
3270
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 62%, transparent) !important;
|
|
3271
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3272
|
-
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3273
|
-
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3274
|
-
}
|
|
3275
|
-
html[data-dsh-glass="on"] [class*="_7yHdaG_header"],
|
|
3276
|
-
html[data-dsh-glass="on"] [class*="_7yHdaG_preview"],
|
|
3277
|
-
html[data-dsh-glass="on"] [class*="_7yHdaG_row"] {
|
|
3278
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3279
|
-
}
|
|
3280
|
-
/* 轮次/历史提问预览:默认隐藏(点开才显示),并去掉头部底色,避免底部露出白条 */
|
|
3281
|
-
.dsh-turn-preview:not(.open){display:none !important;}
|
|
3282
|
-
.dsh-turn-preview-head{background:transparent !important;}
|
|
3283
|
-
|
|
3284
|
-
html[data-dsh-glass="off"] #root [class*="sidebarCol"],
|
|
3285
|
-
html[data-dsh-glass="off"] #root [class*="header"],
|
|
3286
|
-
html[data-dsh-glass="off"] #root [class*="centerCol"],
|
|
3287
|
-
html[data-dsh-glass="off"] #root [class*="wSkVaW_root"],
|
|
3288
|
-
html[data-dsh-glass="off"] #root [class*="wSkVaW_scrollBody"] { background-color: transparent !important; backdrop-filter: none; }
|
|
3289
|
-
/* 会话消息气泡:半透明底(随主题, 60%透明=40%不透明),与 AI 消息(透明/磨砂)区分、融入背景 */
|
|
3290
|
-
html[data-dsh-glass="on"] #root [class*="_bubble"]:not([class*="button"]) {
|
|
3291
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 40%, transparent) !important;
|
|
3292
|
-
}
|
|
3293
|
-
/* 统一桌面 + 浅色主题:DSH 的 secondary/tertiary 文字在浅色磨砂上太浅,统一加深,覆盖状态栏/工具标签等 */
|
|
3294
|
-
html[data-dsh-glass="on"][data-dsh-theme="light"] {
|
|
3295
|
-
--dsw-alias-label-secondary: #33414f !important;
|
|
3296
|
-
--dsw-alias-label-tertiary: #4a5866 !important;
|
|
3297
|
-
}
|
|
3298
|
-
/* 工具调用名(Bash/Shell等)与推理(think)标签:浅色下再显式压深,保证可读 */
|
|
3299
|
-
html[data-dsh-glass="on"][data-dsh-theme="light"] #root [class*="CY-8Ka_title"],
|
|
3300
|
-
html[data-dsh-glass="on"][data-dsh-theme="light"] #root [class*="QWLzLg_title"] {
|
|
3301
|
-
color: #2f3d4a !important;
|
|
3302
|
-
}
|
|
3303
|
-
/* 输入框下方状态/统计行(余额+步数/时长/速度等)都在 composer.dock 槽里:
|
|
3304
|
-
背景是会变的背景图, 单色难保证可读;给整块加半透明深色衬底+亮字, 任意背景都可读。
|
|
3305
|
-
槽位属性可能是 data-slot-conversation 或 data-slot(两种都锁) */
|
|
3306
|
-
html[data-dsh-glass="on"] [data-slot-conversation="conversation.composer.dock"],
|
|
3307
|
-
html[data-dsh-glass="on"] [data-slot="conversation.composer.dock"] {
|
|
3308
|
-
background-color: rgba(12,20,30,.55) !important;
|
|
3309
|
-
border-radius: 8px !important;
|
|
3310
|
-
padding: 1px 10px !important;
|
|
3311
|
-
backdrop-filter: blur(2px);
|
|
3312
|
-
-webkit-backdrop-filter: blur(2px);
|
|
3313
|
-
}
|
|
3314
|
-
html[data-dsh-glass="on"] [data-slot-conversation="conversation.composer.dock"] *,
|
|
3315
|
-
html[data-dsh-glass="on"] [data-slot="conversation.composer.dock"] * {
|
|
3316
|
-
color: #f0f4f9 !important;
|
|
3317
|
-
text-shadow: 0 1px 1px rgba(0,0,0,.5);
|
|
3318
|
-
}
|
|
3319
|
-
/* 会话区所有内容元素背景透明化:去掉"反底色"小块衬底,统一融入磨砂卡片 */
|
|
3320
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] *:not([class*="button"]):not([class*="bubble"]):not([class*="_menu"]) {
|
|
3321
|
-
background-color: transparent !important;
|
|
3322
|
-
}
|
|
3323
|
-
/* 会话区内容衬底(代码块/提示/气泡/引用等)玻璃开启时透明化,融入磨砂卡片,去掉浅色小块衬底 */
|
|
3324
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] [class*="n_block"],
|
|
3325
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] [class*="n_bannerWrap"],
|
|
3326
|
-
|
|
3327
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] [class*="n_plain"],
|
|
3328
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] [class*="n_copyButton"],
|
|
3329
|
-
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] [class*="md-code-block"] {
|
|
3330
|
-
background-color: transparent !important;
|
|
3331
|
-
}
|
|
3332
|
-
/* 输入框下拉/悬浮菜单(如 / 命令、@ 提及):背景给不透明随主题色,避免叠在会话磨砂卡上重叠看不清 */
|
|
3333
|
-
html[data-dsh-glass="on"] #root [class*="_menu"][class*="_menu"][class*="_menu"][class*="_menu"] {
|
|
3334
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 76%, transparent) !important;
|
|
3335
|
-
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 97%, transparent) !important;
|
|
3336
|
-
backdrop-filter: none !important;
|
|
3337
|
-
-webkit-backdrop-filter: none !important;
|
|
3338
|
-
}
|
|
3339
|
-
/* 输入框旁权限/功能选择下拉(盾牌 Full access 等):给不透明底+可读文字(高优先级, 压过 DSH 透明覆盖) */
|
|
3340
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"],
|
|
3341
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"] [class*="_viewport_"],
|
|
3342
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_list_"] [class*="_viewport_"] [class*="_itemWrap_"] {
|
|
3343
|
-
background-color: var(--dsw-specific-input-major, #e8edf3) !important;
|
|
3344
|
-
backdrop-filter: none !important;
|
|
3345
|
-
-webkit-backdrop-filter: none !important;
|
|
3346
|
-
}
|
|
3347
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_item_"],
|
|
3348
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_itemLabel_"] {
|
|
3349
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3350
|
-
}
|
|
3351
|
-
/* 兜底: Radix 风格菜单/下拉(可能被渲染到 body 顶层) */
|
|
3352
|
-
html[data-dsh-glass="on"] [class*="Radix"] [class*="Menu"],
|
|
3353
|
-
html[data-dsh-glass="on"] [class*="radix-menu"],
|
|
3354
|
-
html[data-dsh-glass="on"] [class*="menuitem"],
|
|
3355
|
-
html[data-dsh-glass="on"] [role="menu"] {
|
|
3356
|
-
background-color: var(--dsw-specific-input-major, #e8edf3) !important;
|
|
3357
|
-
backdrop-filter: none !important;
|
|
3358
|
-
-webkit-backdrop-filter: none !important;
|
|
3359
|
-
color: var(--dsw-alias-label-primary) !important;
|
|
3360
|
-
}
|
|
3361
|
-
/* 玻璃开启 + 浅色系统主题:加深弱化标签/元信息文字,避免浅灰字叠在浅磨砂卡上看不清 */
|
|
3362
|
-
@media (prefers-color-scheme: light) {
|
|
3363
|
-
html[data-dsh-glass="on"],
|
|
3364
|
-
html[data-dsh-glass="on"] * {
|
|
3365
|
-
--dsw-alias-label-secondary: #383d44 !important;
|
|
3366
|
-
--dsw-alias-label-tertiary: #50545b !important;
|
|
3367
|
-
}
|
|
3368
|
-
}
|
|
3369
|
-
`
|
|
3135
|
+
// ===== dsh-long-plugins: 设置区排版 CSS(dsh-long 模块开关/补丁状态面板共用)=====
|
|
3136
|
+
// RA-Span(毛玻璃背景图/罩层)已拆分到独立插件 dsh-span;此处仅保留 dsh-long 设置区的排版样式。
|
|
3370
3137
|
const GLASS_LAYOUT_CSS = `
|
|
3371
3138
|
.dsh-glass-section{display:flex;flex-direction:column;gap:16px;min-width:0;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
3372
3139
|
.dsh-glass-head{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
|
@@ -3399,52 +3166,7 @@ window.__ModuleLoader__.load({
|
|
|
3399
3166
|
.dsh-glass-error{padding:10px 12px;border-radius:8px;background:var(--dsw-alias-interactive-bg-hover-danger);color:var(--dsw-alias-state-error-primary);font-size:12px}
|
|
3400
3167
|
.dsh-glass-note{margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
3401
3168
|
`
|
|
3402
|
-
const GLASS_SWATCHES = ['#1a2332', '#0f1420', '#1e3a5f', '#1e5e46', '#5e2e5e', '#6e2323']
|
|
3403
|
-
const glassText = (e) => (e && e.message) ? e.message : String(e)
|
|
3404
|
-
function glassImageInput(value, onChange) {
|
|
3405
|
-
return React.createElement('label', { className: 'dsh-glass-file' },
|
|
3406
|
-
React.createElement('span', null, '上传背景图'),
|
|
3407
|
-
React.createElement('input', { type: 'file', accept: 'image/png,image/jpeg,image/webp,image/gif', onChange: (e) => {
|
|
3408
|
-
const f = e.target.files && e.target.files[0]
|
|
3409
|
-
if (!f) return
|
|
3410
|
-
const reader = new FileReader()
|
|
3411
|
-
reader.onload = () => onChange(String(reader.result || ''))
|
|
3412
|
-
reader.readAsDataURL(f)
|
|
3413
|
-
} }),
|
|
3414
|
-
value ? React.createElement('span', { style: { width: 22, height: 22, borderRadius: 4, backgroundImage: 'url(' + value + ')', backgroundSize: 'cover', display: 'inline-block' } }) : null,
|
|
3415
|
-
)
|
|
3416
|
-
}
|
|
3417
|
-
// 会话区/输入区共用设置卡:罩色 + 罩强度
|
|
3418
|
-
const GlassZoneCard = ({ title, zone, onChange, showGlassToggle }) => {
|
|
3419
|
-
const z = zone || { color: '#1a2332', mask: 0.45, enabled: true }
|
|
3420
|
-
const setPatch = (p) => onChange({ ...z, ...p })
|
|
3421
|
-
return React.createElement('div', { className: 'dsh-glass-card' },
|
|
3422
|
-
React.createElement('div', { className: 'dsh-glass-zone-title' }, title),
|
|
3423
|
-
showGlassToggle ? React.createElement('div', { className: 'dsh-glass-row' },
|
|
3424
|
-
React.createElement('label', { className: 'dsh-glass-switch' }, React.createElement('input', { type: 'checkbox', checked: z.enabled !== false, onChange: (e) => setPatch({ enabled: e.target.checked }) }), title + ' 启用玻璃'),
|
|
3425
|
-
) : null,
|
|
3426
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3427
|
-
React.createElement('label', null, title + ' 罩色'),
|
|
3428
|
-
React.createElement('div', { className: 'dsh-glass-swatches' },
|
|
3429
|
-
GLASS_SWATCHES.map((c) => React.createElement('button', { key: c, type: 'button', className: 'dsh-glass-swatch' + ((z.color || '#1a2332') === c ? ' on' : ''), style: { background: c }, 'aria-label': '颜色 ' + c, onClick: () => setPatch({ color: c }) })),
|
|
3430
|
-
),
|
|
3431
|
-
React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: z.color || '#1a2332', onChange: (e) => setPatch({ color: e.target.value }) })),
|
|
3432
|
-
),
|
|
3433
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3434
|
-
React.createElement('label', null, title + ' 罩强度'),
|
|
3435
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 0.95, step: 0.01, value: z.mask ?? 0.45, onChange: (e) => setPatch({ mask: Number(e.target.value) }) }),
|
|
3436
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((Number(z.mask ?? 0.45)) * 100)}%`),
|
|
3437
|
-
),
|
|
3438
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3439
|
-
React.createElement('label', null, title + ' 透明度'),
|
|
3440
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 1, step: 0.01, value: z.opacity ?? 0.5, onChange: (e) => setPatch({ opacity: Number(e.target.value) }) }),
|
|
3441
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((Number(z.opacity ?? 0.5)) * 100)}%`),
|
|
3442
|
-
),
|
|
3443
|
-
)
|
|
3444
|
-
}
|
|
3445
|
-
// 「dsh-long」设置区:各模块开关(独立、移动端干净),避免把开关塞进 RA-Span
|
|
3446
3169
|
const DSH_LONG_MODULES = [
|
|
3447
|
-
['glass', 'RA-Span'],
|
|
3448
3170
|
['uploadAttach', '附件上传(回形针)'],
|
|
3449
3171
|
['uploadDragDrop', '附件拖放上传'],
|
|
3450
3172
|
['uploadPaste', '附件粘贴上传'],
|
|
@@ -3460,7 +3182,7 @@ window.__ModuleLoader__.load({
|
|
|
3460
3182
|
setState((s) => ({ ...s, loading: true, error: '', patches: null }))
|
|
3461
3183
|
try {
|
|
3462
3184
|
const [r, pr] = await Promise.all([
|
|
3463
|
-
fetch('/api/dsh-uploads/
|
|
3185
|
+
fetch('/api/dsh-uploads/modules-config', { cache: 'no-store' }),
|
|
3464
3186
|
fetch('/api/dsh-uploads/patch-status', { cache: 'no-store' }),
|
|
3465
3187
|
])
|
|
3466
3188
|
const b = await r.json()
|
|
@@ -3475,7 +3197,7 @@ window.__ModuleLoader__.load({
|
|
|
3475
3197
|
const save = React.useCallback(async () => {
|
|
3476
3198
|
setState((s) => ({ ...s, saving: true, error: '' }))
|
|
3477
3199
|
try {
|
|
3478
|
-
const r = await fetch('/api/dsh-uploads/
|
|
3200
|
+
const r = await fetch('/api/dsh-uploads/modules-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ modules: state.modules }) })
|
|
3479
3201
|
const b = await r.json()
|
|
3480
3202
|
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3481
3203
|
window.__dshLongModules = state.modules
|
|
@@ -3515,526 +3237,12 @@ window.__ModuleLoader__.load({
|
|
|
3515
3237
|
) : null,
|
|
3516
3238
|
)
|
|
3517
3239
|
}
|
|
3518
|
-
const GlassSettingsSection = () => {
|
|
3519
|
-
const [state, setState] = React.useState({ loading: true, saving: false, error: '', cfg: null, bgImage: '', bgList: [] })
|
|
3520
|
-
const load = React.useCallback(async () => {
|
|
3521
|
-
setState((s) => ({ ...s, loading: true, error: '' }))
|
|
3522
|
-
try {
|
|
3523
|
-
const r = await fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' })
|
|
3524
|
-
const b = await r.json()
|
|
3525
|
-
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3526
|
-
let list = []
|
|
3527
|
-
try { const rb = await fetch('/api/dsh-uploads/glass-backgrounds', { cache: 'no-store' }); const jb = await rb.json(); list = jb.list || [] } catch (_) {}
|
|
3528
|
-
setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.bgImageUrl || '', bgList: list }))
|
|
3529
|
-
} catch (e) { setState((s) => ({ ...s, loading: false, error: glassText(e) })) }
|
|
3530
|
-
}, [])
|
|
3531
|
-
React.useEffect(() => { load(); }, [load])
|
|
3532
|
-
const patch = React.useCallback((p) => setState((s) => ({ ...s, cfg: { ...(s.cfg || {}), ...p } })), [])
|
|
3533
|
-
const patchZone = React.useCallback((key, p) => setState((s) => ({ ...s, cfg: { ...(s.cfg || {}), [key]: p } })), [])
|
|
3534
|
-
// 输入框设置:按主题(theme)合并进 cfg.inputBox[theme](不整体覆盖、不丢另一主题)
|
|
3535
|
-
const patchInput = React.useCallback((theme, p) => setState((s) => {
|
|
3536
|
-
const box = { light: { color: '', opacity: 1 }, dark: { color: '', opacity: 1 }, ...((s.cfg && s.cfg.inputBox) || {}) }
|
|
3537
|
-
box[theme] = { ...((box[theme]) || { color: '', opacity: 1 }), ...p }
|
|
3538
|
-
return { ...s, cfg: { ...(s.cfg || {}), inputBox: box } }
|
|
3539
|
-
}), [])
|
|
3540
|
-
// 背景罩设置:按主题合并进 cfg.bgTint[theme]
|
|
3541
|
-
const patchBgTint = React.useCallback((theme, p) => setState((s) => {
|
|
3542
|
-
const box = { light: { color: '#1a2332', mask: 0.28 }, dark: { color: '#1a2332', mask: 0.28 }, ...((s.cfg && s.cfg.bgTint) || {}) }
|
|
3543
|
-
box[theme] = { ...((box[theme]) || { color: '#1a2332', mask: 0.28 }), ...p }
|
|
3544
|
-
return { ...s, cfg: { ...(s.cfg || {}), bgTint: box } }
|
|
3545
|
-
}), [])
|
|
3546
|
-
const save = React.useCallback(async () => {
|
|
3547
|
-
setState((s) => ({ ...s, saving: true, error: '' }))
|
|
3548
|
-
try {
|
|
3549
|
-
const cfg = state.cfg || {}
|
|
3550
|
-
const num = (v, fb) => ((typeof v === 'number' && Number.isFinite(v) && v >= 0) ? v : fb)
|
|
3551
|
-
// 背景图: 仅当用户上传了新图(data-URI)才传; 未改(URL)省略以保持引用; 清空传 '' 清除
|
|
3552
|
-
const sBg = state.bgImage
|
|
3553
|
-
let bgImageOut
|
|
3554
|
-
if (sBg === '') bgImageOut = ''
|
|
3555
|
-
else if (typeof sBg === 'string' && sBg.startsWith('data:image/')) bgImageOut = sBg
|
|
3556
|
-
else bgImageOut = undefined
|
|
3557
|
-
const payload = {
|
|
3558
|
-
enabled: !!cfg.enabled, blur: num(cfg.blur, 0),
|
|
3559
|
-
bgMask: num(cfg.bgMask, 0.28), bgColor: cfg.bgColor || '#1a2332', bgBlur: num(cfg.bgBlur, 0), bgOpacity: num(cfg.bgOpacity, 1),
|
|
3560
|
-
...(bgImageOut !== undefined ? { bgImage: bgImageOut } : {}),
|
|
3561
|
-
bgTint: {
|
|
3562
|
-
light: { color: (cfg.bgTint?.light?.color) || '#1a2332', mask: num(cfg.bgTint?.light?.mask, 0.28) },
|
|
3563
|
-
dark: { color: (cfg.bgTint?.dark?.color) || '#1a2332', mask: num(cfg.bgTint?.dark?.mask, 0.28) },
|
|
3564
|
-
},
|
|
3565
|
-
inputBox: {
|
|
3566
|
-
light: { color: (cfg.inputBox?.light?.color) || '', opacity: num(cfg.inputBox?.light?.opacity, 1) },
|
|
3567
|
-
dark: { color: (cfg.inputBox?.dark?.color) || '', opacity: num(cfg.inputBox?.dark?.opacity, 1) },
|
|
3568
|
-
},
|
|
3569
|
-
zone: {
|
|
3570
|
-
session: { enabled: cfg.session && cfg.session.enabled !== false, color: (cfg.session && cfg.session.color) || '#1a2332', mask: num(cfg.session && cfg.session.mask, 0.45), opacity: num(cfg.session && cfg.session.opacity, 0.5) },
|
|
3571
|
-
input: { color: (cfg.input && cfg.input.color) || '#1a2332', mask: num(cfg.input && cfg.input.mask, 0.6), opacity: num(cfg.input && cfg.input.opacity, 0.55) },
|
|
3572
|
-
},
|
|
3573
|
-
}
|
|
3574
|
-
const r = await fetch('/api/dsh-uploads/glass-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
|
|
3575
|
-
const b = await r.json()
|
|
3576
|
-
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3577
|
-
const applyBg = (payload.bgImage && payload.bgImage.startsWith('data:')) ? payload.bgImage : ((state.bgImage && state.bgImage.startsWith('/')) ? state.bgImage : '')
|
|
3578
|
-
window.__dshGlassApply && window.__dshGlassApply({ ...payload, bgImage: applyBg })
|
|
3579
|
-
setState((s) => ({ ...s, saving: false, cfg: { ...(s.cfg || {}), ...payload }, bgImage: applyBg, error: '' }))
|
|
3580
|
-
} catch (e) { setState((s) => ({ ...s, saving: false, error: glassText(e) })) }
|
|
3581
|
-
}, [state])
|
|
3582
|
-
const cfg = state.cfg || {}
|
|
3583
|
-
if (state.loading) return React.createElement('div', { className: 'dsh-glass-section' }, '加载中…')
|
|
3584
|
-
return React.createElement('div', { className: 'dsh-glass-section' },
|
|
3585
|
-
React.createElement('div', { className: 'dsh-glass-head' },
|
|
3586
|
-
React.createElement('div', null, React.createElement('h2', null, 'RA-Span'), React.createElement('p', null, '共用一张背景图铺满整页(RA-Span):背景图、背景罩层颜色、背景模糊/透明度可调;会话区/输入区按需透明或实底。')),
|
|
3587
|
-
React.createElement('label', { className: 'dsh-glass-switch' }, React.createElement('input', { type: 'checkbox', checked: !!cfg.enabled, onChange: (e) => patch({ enabled: e.target.checked }) }), cfg.enabled ? '已开启' : '已关闭'),
|
|
3588
|
-
),
|
|
3589
|
-
state.error ? React.createElement('div', { className: 'dsh-glass-error' }, state.error) : null,
|
|
3590
|
-
React.createElement('div', { className: 'dsh-glass-card' },
|
|
3591
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3592
|
-
React.createElement('label', null, '背景图'),
|
|
3593
|
-
React.createElement('div', { className: 'dsh-glass-bg' }, glassImageInput(state.bgImage, (v) => setState((s) => ({ ...s, bgImage: v })))),
|
|
3594
|
-
state.bgList && state.bgList.length ? React.createElement('select', { className: 'dsh-glass-file', value: '', style: { marginLeft: 8 }, onChange: async (e) => {
|
|
3595
|
-
const name = e.target.value; if (!name) return
|
|
3596
|
-
try { const rb = await fetch('/api/dsh-uploads/glass-backgrounds?name=' + encodeURIComponent(name), { cache: 'no-store' }); const uri = await rb.text(); if (uri && uri.startsWith('data:image/')) setState((s) => ({ ...s, bgImage: uri })) } catch (_) {}
|
|
3597
|
-
} }, React.createElement('option', { value: '' }, '选择旧图…'), state.bgList.map((n, i) => React.createElement('option', { key: n, value: n }, '背景图 ' + (i + 1))) ) : null,
|
|
3598
|
-
),
|
|
3599
|
-
React.createElement('div', { className: 'dsh-glass-card' },
|
|
3600
|
-
React.createElement('div', { className: 'dsh-glass-zone-title' }, '背景罩'),
|
|
3601
|
-
[['light', '浅色主题', '#1a2332'], ['dark', '深色主题', '#1a2332']].map(([t, label, native]) => {
|
|
3602
|
-
const bt = (cfg.bgTint && cfg.bgTint[t]) || { color: native, mask: 0.28 }
|
|
3603
|
-
return React.createElement('div', { key: t, className: 'dsh-glass-input-theme' },
|
|
3604
|
-
React.createElement('div', { className: 'dsh-glass-theme-label' }, label),
|
|
3605
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3606
|
-
React.createElement('label', null, '罩色'),
|
|
3607
|
-
React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: bt.color || native, onChange: (e) => patchBgTint(t, { color: e.target.value }) })),
|
|
3608
|
-
),
|
|
3609
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3610
|
-
React.createElement('label', null, '罩强度'),
|
|
3611
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 0.95, step: 0.01, value: (typeof bt.mask === 'number') ? bt.mask : 0.28, onChange: (e) => patchBgTint(t, { mask: Number(e.target.value) }) }),
|
|
3612
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((typeof bt.mask === 'number' ? bt.mask : 0.28) * 100)}%`),
|
|
3613
|
-
),
|
|
3614
|
-
)
|
|
3615
|
-
}),
|
|
3616
|
-
React.createElement('p', { className: 'dsh-glass-note' }, '浅色主题与深色主题可分别设置背景罩颜色与罩强度,切换主题时自动应用对应的一套。'),
|
|
3617
|
-
),
|
|
3618
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3619
|
-
React.createElement('label', null, '背景图模糊'),
|
|
3620
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 60, value: cfg.bgBlur ?? 0, onChange: (e) => patch({ bgBlur: Number(e.target.value) }) }),
|
|
3621
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Number(cfg.bgBlur ?? 0)} px`),
|
|
3622
|
-
),
|
|
3623
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3624
|
-
React.createElement('label', null, '背景透明度'),
|
|
3625
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 100, value: Math.round((1 - (Number(cfg.bgOpacity ?? 1))) * 100), onChange: (e) => patch({ bgOpacity: 1 - Number(e.target.value) / 100 }) }),
|
|
3626
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((1 - (Number(cfg.bgOpacity ?? 1))) * 100)}%`),
|
|
3627
|
-
),
|
|
3628
|
-
React.createElement('div', { className: 'dsh-glass-card' },
|
|
3629
|
-
React.createElement('div', { className: 'dsh-glass-zone-title' }, '面板'),
|
|
3630
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3631
|
-
React.createElement('label', null, '面板磨砂'),
|
|
3632
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 80, value: cfg.blur ?? 20, onChange: (e) => patch({ blur: Number(e.target.value) }) }),
|
|
3633
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Number(cfg.blur ?? 20)} px`),
|
|
3634
|
-
),
|
|
3635
|
-
React.createElement('p', { className: 'dsh-glass-note' }, '作用于弹窗/任务卡等面板的磨砂玻璃强度(0 = 关闭磨砂)。'),
|
|
3636
|
-
),
|
|
3637
|
-
React.createElement('div', { className: 'dsh-glass-card' },
|
|
3638
|
-
React.createElement('div', { className: 'dsh-glass-zone-title' }, '输入框'),
|
|
3639
|
-
[['light', '浅色主题', '#ffffff'], ['dark', '深色主题', '#1c2530']].map(([t, label, native]) => {
|
|
3640
|
-
const tb = (cfg.inputBox && cfg.inputBox[t]) || { color: '', opacity: 1 }
|
|
3641
|
-
return React.createElement('div', { key: t, className: 'dsh-glass-input-theme' },
|
|
3642
|
-
React.createElement('div', { className: 'dsh-glass-theme-label' }, label),
|
|
3643
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3644
|
-
React.createElement('label', null, '输入框颜色'),
|
|
3645
|
-
React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: tb.color || native, onChange: (e) => patchInput(t, { color: e.target.value }) })),
|
|
3646
|
-
React.createElement('button', { type: 'button', style: { marginLeft: 8, fontSize: 12 }, onClick: () => patchInput(t, { color: '', opacity: 1 }) }, '原生'),
|
|
3647
|
-
),
|
|
3648
|
-
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3649
|
-
React.createElement('label', null, '透明度'),
|
|
3650
|
-
React.createElement('input', { type: 'range', className: 'dsh-glass-range', min: 0, max: 100, value: Math.round((1 - (typeof tb.opacity === 'number' ? tb.opacity : 1)) * 100), onChange: (e) => patchInput(t, { opacity: 1 - Number(e.target.value) / 100 }) }),
|
|
3651
|
-
React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((1 - (typeof tb.opacity === 'number' ? tb.opacity : 1)) * 100)}%`),
|
|
3652
|
-
),
|
|
3653
|
-
)
|
|
3654
|
-
}),
|
|
3655
|
-
React.createElement('p', { className: 'dsh-glass-note' }, '浅色主题与深色主题可分别设置。0% = 全不透明,100% = 全透明;"原生" = 跟随该主题的 DSH 原生色。文字颜色按背景亮度自动配深/浅,保证可读。'),
|
|
3656
|
-
),
|
|
3657
|
-
React.createElement('p', { className: 'dsh-glass-note' }, '提示:背景图铺满整页(可加背景图罩);背景图模糊/透明度单独可调;会话区使用自己的罩色、罩强度与透明度;左栏/顶部透出背景图;输入框颜色/不透明度可单独设置。'),
|
|
3658
|
-
React.createElement('div', { className: 'dsh-glass-actions' },
|
|
3659
|
-
React.createElement('button', { type: 'button', onClick: load }, '重置'),
|
|
3660
|
-
React.createElement('button', { type: 'button', className: 'primary', disabled: state.saving, onClick: save }, state.saving ? '保存中…' : '保存生效'),
|
|
3661
|
-
),
|
|
3662
|
-
),
|
|
3663
|
-
)
|
|
3664
|
-
}
|
|
3665
|
-
const glassPlugin = {
|
|
3666
|
-
inject: ['slots'],
|
|
3667
|
-
apply(ctx) {
|
|
3668
|
-
const hexToRgb = (hex) => { const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(String(hex || '')); return m ? `${parseInt(m[1],16)},${parseInt(m[2],16)},${parseInt(m[3],16)}` : '15,20,32' }
|
|
3669
|
-
ctx.effect(() => {
|
|
3670
|
-
const style = document.createElement('style')
|
|
3671
|
-
style.dataset.plugin = 'dsh-long-plugins'
|
|
3672
|
-
style.dataset.pluginCss = 'dsh-long-plugins/glass'
|
|
3673
|
-
style.textContent = GLASS_CSS // 玻璃视觉规则(仅 RA-Span 开启时注入)
|
|
3674
|
-
document.head.appendChild(style)
|
|
3675
|
-
return () => style.remove()
|
|
3676
|
-
}, 'dsh-long-plugins: glass styles')
|
|
3677
|
-
ctx.effect(() => {
|
|
3678
|
-
const root = document.documentElement
|
|
3679
|
-
|
|
3680
|
-
// 磨砂卡片与底部输入卡片对齐:宽度取输入卡片宽度,translateX 补偿使左右边界对齐输入卡。
|
|
3681
|
-
const centerScroll = () => {
|
|
3682
|
-
const el = document.querySelector('#root [class*="wSkVaW_scrollBody"]')
|
|
3683
|
-
if (!el) return
|
|
3684
|
-
const card = document.querySelector('#root [class*="uV2eYG_card"]')
|
|
3685
|
-
const r = el.getBoundingClientRect()
|
|
3686
|
-
const target = card ? card.getBoundingClientRect() : null
|
|
3687
|
-
// 不改磨砂卡片宽度(保留默认 width:min() 原始宽度),仅水平居中到输入卡片中心。
|
|
3688
|
-
const diff = (target ? (target.left + target.right) / 2 : window.innerWidth / 2) - (r.left + r.right) / 2
|
|
3689
|
-
el.style.transform = 'translateX(' + Math.round(diff) + 'px)'
|
|
3690
|
-
}
|
|
3691
|
-
window.addEventListener('resize', centerScroll)
|
|
3692
|
-
window.__dshGlassCenter = centerScroll
|
|
3693
|
-
// 主题切换监听:观察 <html> 的 style/class;只创建一次(自触发的写入在 applyTheme 内先断开再重连,杜绝死循环)
|
|
3694
|
-
window.__dshGlassStyleObs = null
|
|
3695
|
-
if (!window.__dshGlassStyleObs) {
|
|
3696
|
-
window.__dshGlassStyleObs = new MutationObserver(() => window.__dshGlassApplyTheme && window.__dshGlassApplyTheme())
|
|
3697
|
-
window.__dshGlassStyleObs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
|
|
3698
|
-
}
|
|
3699
|
-
const themeMq = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)')
|
|
3700
|
-
const themeMqHandler = () => window.__dshGlassApplyTheme && window.__dshGlassApplyTheme()
|
|
3701
|
-
if (themeMq) themeMq.addEventListener('change', themeMqHandler)
|
|
3702
|
-
// win 风格文字:给左栏/顶栏内的可见文字元素加白色+深色描边;跳过弹窗(modal/overlay/dialog/settings/panel)内元素。
|
|
3703
|
-
|
|
3704
|
-
window.__dshGlassApply = (cfg) => {
|
|
3705
|
-
const c = cfg || {}
|
|
3706
|
-
// 手机端/窄屏(<=768px):不开启毛玻璃,还原 DSH 原生样式(清背景图/罩变量/磨砂)。
|
|
3707
|
-
if (window.innerWidth <= 768) {
|
|
3708
|
-
root.setAttribute('data-dsh-glass', 'off')
|
|
3709
|
-
root.setAttribute('data-dsh-session-glass', 'off')
|
|
3710
|
-
root.style.setProperty('--dsh-glass-blur', '')
|
|
3711
|
-
root.style.setProperty('--dsh-glass-session-bg', '')
|
|
3712
|
-
root.style.setProperty('--dsh-glass-input-bg', '')
|
|
3713
|
-
root.style.setProperty('--dsh-glass-bg-zone', '')
|
|
3714
|
-
const b = document.body
|
|
3715
|
-
b.style.backgroundImage = 'none'; b.style.backgroundSize = ''; b.style.backgroundPosition = ''; b.style.backgroundRepeat = ''
|
|
3716
|
-
const fr = document.querySelector('#root > div > div') || document.querySelector('#root > div')
|
|
3717
|
-
if (fr) { fr.style.backgroundColor = ''; fr.style.backgroundImage = 'none'; fr.style.backgroundSize = ''; fr.style.backgroundPosition = ''; fr.style.backgroundRepeat = '' }
|
|
3718
|
-
const bgEl = document.querySelector('.dsh-glass-bgimg'); if (bgEl) { bgEl.style.display = 'none' }
|
|
3719
|
-
return
|
|
3720
|
-
}
|
|
3721
|
-
// 读 DSH 真实主题(DSH 用 inline `color-scheme` 写在 <html style> 上,非 OS 偏好)
|
|
3722
|
-
const dshDark = () => /dark/i.test(getComputedStyle(document.documentElement).colorScheme)
|
|
3723
|
-
// 颜色亮度(0~1):用来自动决定文字用深色还是浅色,保证可读
|
|
3724
|
-
const relLum = (hex) => {
|
|
3725
|
-
const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(String(hex || ''))
|
|
3726
|
-
if (!m) return 0.5
|
|
3727
|
-
const map = (v) => { const s = parseInt(v, 16) / 255; return s <= 0.03928 ? s / 12.92 : Math.pow((s + 0.055) / 1.055, 2.4) }
|
|
3728
|
-
return 0.2126 * map(m[1]) + 0.7152 * map(m[2]) + 0.0722 * map(m[3])
|
|
3729
|
-
}
|
|
3730
|
-
root.setAttribute('data-dsh-glass', c.enabled ? 'on' : 'off')
|
|
3731
|
-
const sessOn = !(c.zone && c.zone.session && c.zone.session.enabled === false)
|
|
3732
|
-
root.setAttribute('data-dsh-session-glass', sessOn ? 'on' : 'off')
|
|
3733
|
-
const blurn = Number(c.blur); const blur = Math.max(0, Math.min(80, Number.isFinite(blurn) ? blurn : 16))
|
|
3734
|
-
root.style.setProperty('--dsh-glass-blur', blur + 'px')
|
|
3735
|
-
const zs = (c.zone && c.zone.session) || { color: '#1a2332', mask: 0.45, opacity: 0.5 }
|
|
3736
|
-
const zi = (c.zone && c.zone.input) || { color: '#1a2332', mask: 0.6, opacity: 0.55 }
|
|
3737
|
-
const smn = Number(zs.mask); const sm = Math.min(0.95, Math.max(0, Number.isFinite(smn) ? smn : 0.45))
|
|
3738
|
-
root.style.setProperty('--dsh-glass-session-bg', `rgba(${hexToRgb(zs.color)},${sm})`)
|
|
3739
|
-
const imn = Number(zi.mask); const im = Math.min(0.95, Math.max(0, Number.isFinite(imn) ? imn : 0.6))
|
|
3740
|
-
root.style.setProperty('--dsh-glass-input-bg', `rgba(${hexToRgb(zi.color)},${im})`)
|
|
3741
|
-
// 透明度=透明程度:越大越透明(100%=>雾面无填充,完全透出背景)
|
|
3742
|
-
const sop = Math.min(1, Math.max(0, Number.isFinite(Number(zs.opacity)) ? Number(zs.opacity) : 0.5))
|
|
3743
|
-
root.style.setProperty('--dsh-glass-session-white', `color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) ${Math.round((1 - sop) * 100)}%, transparent)`)
|
|
3744
|
-
const iop = Math.min(1, Math.max(0, Number.isFinite(Number(zi.opacity)) ? Number(zi.opacity) : 0.55))
|
|
3745
|
-
root.style.setProperty('--dsh-glass-input-white', `color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) ${Math.round((1 - iop) * 100)}%, transparent)`)
|
|
3746
|
-
|
|
3747
|
-
// 背景图罩(整页背景图上叠一层半透明罩色):浅/深主题各一套 color+mask;
|
|
3748
|
-
// 兼容旧版单套 bgColor/bgMask。
|
|
3749
|
-
const bgTint = (tk) => {
|
|
3750
|
-
const t = (c.bgTint && c.bgTint[tk]) || { color: c.bgColor || '#1a2332', mask: c.bgMask ?? 0.28 }
|
|
3751
|
-
const mask = Math.min(0.95, Math.max(0, Number.isFinite(Number(t && t.mask)) ? Number(t.mask) : 0.28))
|
|
3752
|
-
return { color: (t && t.color) || '#1a2332', mask }
|
|
3753
|
-
}
|
|
3754
|
-
// 给输入框卡片设内联实底+文字色,且用 !important,
|
|
3755
|
-
// 因为 DSH 核心样式用 background-color: transparent !important 压过普通内联样式;
|
|
3756
|
-
// 内联 !important 优先级最高,一定覆盖。颜色/不透明度来自配置 inputBox。
|
|
3757
|
-
const applyInputCard = () => {
|
|
3758
|
-
const inputCard = document.querySelector('#root [class*="uV2eYG_card"]')
|
|
3759
|
-
if (!inputCard) return
|
|
3760
|
-
const dark = dshDark()
|
|
3761
|
-
const ib = (c.inputBox && typeof c.inputBox === 'object') ? c.inputBox : {}
|
|
3762
|
-
// 浅色/深色各一套配置;兼容旧版单套 inputBox:{color,opacity}
|
|
3763
|
-
const tb = (ib.light && ib.dark) ? (dark ? ib.dark : ib.light) : ib
|
|
3764
|
-
const defBg = dark ? '#1c2530' : '#ffffff'
|
|
3765
|
-
// color: 空串/非法 => 用主题原生色;否则用用户指定的颜色
|
|
3766
|
-
const hasColor = tb && typeof tb.color === 'string' && /^#/.test(tb.color)
|
|
3767
|
-
const bgHex = hasColor ? tb.color : defBg
|
|
3768
|
-
const alpha = (tb && typeof tb.opacity === 'number' && Number.isFinite(tb.opacity)) ? Math.min(1, Math.max(0, tb.opacity)) : 1
|
|
3769
|
-
const bg = `rgba(${hexToRgb(bgHex)},${alpha})`
|
|
3770
|
-
// 背景/文字同步:按背景底色亮度决定文字色(亮底=深字/暗底=浅字),确保可读;与 v2.2.1 一致
|
|
3771
|
-
const fg = relLum(bgHex) > 0.5 ? '#1a2332' : '#f5f7fa'
|
|
3772
|
-
inputCard.style.setProperty('background-color', bg, 'important')
|
|
3773
|
-
inputCard.style.setProperty('color', fg, 'important')
|
|
3774
|
-
// 子层(scroll/row)也是透明的,继承卡片文字色即可,确保可读
|
|
3775
|
-
inputCard.querySelectorAll('[class*="uV2eYG_scroll"],[class*="uV2eYG_row"]').forEach(el => {
|
|
3776
|
-
el.style.setProperty('color', fg, 'important')
|
|
3777
|
-
})
|
|
3778
|
-
}
|
|
3779
|
-
// 主界面左栏/顶栏可见文字:白字+单层轻投影(Windows 桌面图标风格, 不加粗)。
|
|
3780
|
-
// 显式跳过设置/弹窗/浮层(modal/overlay/dialog/settings/panel/drawer/Radix/menu), 避免波及设置面板/弹出菜单。
|
|
3781
|
-
const SKIP_SEL = ':is([class*="settings"],[class*="Dialog"],[class*="dialog"],[class*="modal"],[class*="overlay"],[class*="_panel"],[class*="panel"],[class*="drawer"],[class*="Radix"],[class*="_menu"],[class*="brand"],[data-chain-overlay-fallback],[class*="M8wy4a_"])'
|
|
3782
|
-
const isInSkip = (el) => { let p = el; while (p && p !== document.documentElement) { if (p.matches && p.matches(SKIP_SEL)) return true; p = p.parentElement } return false }
|
|
3783
|
-
const applyChromeText = () => {
|
|
3784
|
-
if (!c.enabled) return
|
|
3785
|
-
// 只处理主界面左栏/顶栏内带文字的元素;跳过图标/纯容器。
|
|
3786
|
-
// 文字检测放宽:button/a/span/li/p/标题等直接算,纯 div 容器需自身带文本才算,
|
|
3787
|
-
// 这样底部「设置/已归档」等包在子元素里的按钮也能命中。
|
|
3788
|
-
const roots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
|
|
3789
|
-
roots.forEach((root) => {
|
|
3790
|
-
if (isInSkip(root)) return
|
|
3791
|
-
root.querySelectorAll('div,span,button,a,label,p,li,h1,h2,h3,h4').forEach((el) => {
|
|
3792
|
-
if (isInSkip(el)) return
|
|
3793
|
-
if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过
|
|
3794
|
-
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3795
|
-
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(el.tagName)
|
|
3796
|
-
if (!textLike && !ownText) return
|
|
3797
|
-
// 跳过有盒子背景的元素(用类名判断, 不再 getComputedStyle 以免每元素强制样式重算导致卡顿)
|
|
3798
|
-
if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain|button/i.test(el.className)) return
|
|
3799
|
-
el.style.setProperty('color', '#ffffff', 'important')
|
|
3800
|
-
el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
|
|
3801
|
-
if (el.dataset) el.dataset.dshgl = '1'
|
|
3802
|
-
})
|
|
3803
|
-
// 图标也变白(随 currentColor),用于工作区文件夹等单色图标;保留显式品牌色
|
|
3804
|
-
root.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
|
|
3805
|
-
if (isInSkip(el)) return
|
|
3806
|
-
el.style.setProperty('color', '#ffffff', 'important')
|
|
3807
|
-
el.querySelectorAll('path,use,rect,circle,line,polyline,ellipse').forEach((p) => {
|
|
3808
|
-
const f = (p.getAttribute('fill') || '').toLowerCase()
|
|
3809
|
-
if (f && f !== 'none' && f !== 'currentcolor') return
|
|
3810
|
-
p.style.setProperty('fill', 'currentColor', 'important')
|
|
3811
|
-
const s = (p.getAttribute('stroke') || '').toLowerCase()
|
|
3812
|
-
if (s && s !== 'none' && s !== 'currentcolor') return
|
|
3813
|
-
p.style.setProperty('stroke', 'currentColor', 'important')
|
|
3814
|
-
})
|
|
3815
|
-
})
|
|
3816
|
-
})
|
|
3817
|
-
}
|
|
3818
|
-
// 会话区内容(尤其 AI 过程内容:Bash/Think 标签、思考、工具输出等浅色灰字)
|
|
3819
|
-
// 在透明背景图上看不清;用同侧边栏「白字+深投影」处理,跳过有盒子背景的元素(用户气泡/代码块衬底等)。
|
|
3820
|
-
const applySessionText = () => {
|
|
3821
|
-
if (!c.enabled) return
|
|
3822
|
-
const scroll = document.querySelector('#root [class*="wSkVaW_scrollBody"]')
|
|
3823
|
-
if (!scroll) return
|
|
3824
|
-
scroll.querySelectorAll('div,span,p,li,pre,code,button,a,label,h1,h2,h3,h4').forEach((el) => {
|
|
3825
|
-
if (isInSkip(el)) return
|
|
3826
|
-
if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过(只处理新内容)
|
|
3827
|
-
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3828
|
-
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4|PRE|CODE)$/.test(el.tagName)
|
|
3829
|
-
if (!textLike && !ownText) return
|
|
3830
|
-
if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain/i.test(el.className)) return
|
|
3831
|
-
el.style.setProperty('color', '#ffffff', 'important')
|
|
3832
|
-
el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
|
|
3833
|
-
if (el.dataset) el.dataset.dshgl = '1'
|
|
3834
|
-
})
|
|
3835
|
-
// 在白字底上, 给过程内容配色区分, Error 用红色。
|
|
3836
|
-
// 文字常在标签的子元素里(白字已把它设白), 故连同文本子元素一起上色。
|
|
3837
|
-
const paint = (el, color) => {
|
|
3838
|
-
if (!el) return
|
|
3839
|
-
const set = (d) => { if (d.textContent && d.textContent.trim()) d.style.setProperty('color', color, 'important') }
|
|
3840
|
-
set(el)
|
|
3841
|
-
el.querySelectorAll('span,div,p,li,a').forEach(set)
|
|
3842
|
-
}
|
|
3843
|
-
// 错误/失败单行文本 → 红(只判短行、以 Error/错误/失败/异常 开头, 避免整段误标红)
|
|
3844
|
-
const isErrText = (d) => {
|
|
3845
|
-
const t = (d.textContent || '').trim()
|
|
3846
|
-
if (!t || t.length > 120) return false
|
|
3847
|
-
return /^(error|错误|失败|异常|fail)\b/i.test(t) || /^(error|错误|失败|异常)\s*[::]/i.test(t) || /^File not found/i.test(t)
|
|
3848
|
-
}
|
|
3849
|
-
scroll.querySelectorAll('div,span,p,li,pre,code').forEach((el) => {
|
|
3850
|
-
if (!isErrText(el)) return
|
|
3851
|
-
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3852
|
-
if (ownText) el.style.setProperty('color', '#ff6b6b', 'important')
|
|
3853
|
-
})
|
|
3854
|
-
// 工具名标签(o3BgMG_title):按工具类型配色(Bash/Shell=绿, Read=蓝, Edit=橙, Write=紫, 默认蓝)
|
|
3855
|
-
const toolColor = (text) => {
|
|
3856
|
-
const t = (text || '').toLowerCase()
|
|
3857
|
-
if (/bash|shell|zsh|terminal|powershell|cmd|sh\b/i.test(t)) return '#5ec26b'
|
|
3858
|
-
if (/edit|modify/i.test(t)) return '#f0a860'
|
|
3859
|
-
if (/write|create/i.test(t)) return '#c9a0ff'
|
|
3860
|
-
return '#8ccfff'
|
|
3861
|
-
}
|
|
3862
|
-
scroll.querySelectorAll('[class*="o3BgMG_title"],[class*="CY-8Ka_title"]').forEach((title) => {
|
|
3863
|
-
paint(title, toolColor(title.textContent))
|
|
3864
|
-
// 从标签向上找最近一个"含链接/代码/更多文本"的容器作为工具行,找不到就用父级
|
|
3865
|
-
let box = title.parentElement
|
|
3866
|
-
for (let i = 0; i < 4 && box; i++) {
|
|
3867
|
-
if (box.querySelector && box.querySelector('a, code, [class*="desc"], [class*="path"], [class*="cmd"], [class*="arg"]')) break
|
|
3868
|
-
box = box.parentElement
|
|
3869
|
-
}
|
|
3870
|
-
if (!box) box = title.parentElement
|
|
3871
|
-
box.querySelectorAll('div,span,a,p,li,pre,code').forEach((el) => {
|
|
3872
|
-
if (el === title || el.querySelector('[class*="o3BgMG_title"]')) return
|
|
3873
|
-
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3874
|
-
if (!ownText || isErrText(el)) return
|
|
3875
|
-
paint(el, '#6fb89a')
|
|
3876
|
-
})
|
|
3877
|
-
})
|
|
3878
|
-
// 工具输出/路径(o3BgMG_summary):成功 → 水绿;errorSummary 保持红
|
|
3879
|
-
scroll.querySelectorAll('[class*="o3BgMG_summary"]').forEach((el) => {
|
|
3880
|
-
if (/errorSummary/i.test(String(el.className))) return
|
|
3881
|
-
paint(el, '#6fb89a')
|
|
3882
|
-
})
|
|
3883
|
-
// 路径/文件链接(o3BgMG_fileLink) → 水绿(与白字标签区分)
|
|
3884
|
-
scroll.querySelectorAll('[class*="o3BgMG_fileLink"]').forEach((el) => paint(el, '#6fb89a'))
|
|
3885
|
-
// 思考正文(think 容器) → 暖琥珀;再覆盖 Think 标签为琥珀。
|
|
3886
|
-
// 全文档查找,不受 scrollBody 位置/加载时序影响(仍跳过设置/菜单/浮层)
|
|
3887
|
-
const paintThinkBody = (root) => {
|
|
3888
|
-
if (!root) return
|
|
3889
|
-
root.querySelectorAll('div,span,p,li').forEach((el) => {
|
|
3890
|
-
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3891
|
-
if (ownText && !isErrText(el) && !isInSkip(el)) paint(el, '#e6c583')
|
|
3892
|
-
})
|
|
3893
|
-
}
|
|
3894
|
-
const thinkRoots = [...document.querySelectorAll('[class*="QWLzLg_root"]')].filter((r) => !isInSkip(r))
|
|
3895
|
-
thinkRoots.forEach(paintThinkBody)
|
|
3896
|
-
document.querySelectorAll('[class*="QWLzLg_title"]').forEach((el) => {
|
|
3897
|
-
if (!isInSkip(el)) paint(el, '#f2c566')
|
|
3898
|
-
})
|
|
3899
|
-
// 会话区工具调用等图标:改为白色清晰显示(单色图标随 currentColor 变白, 保留品牌色)
|
|
3900
|
-
scroll.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
|
|
3901
|
-
if (isInSkip(el)) return
|
|
3902
|
-
el.style.setProperty('color', '#ffffff', 'important')
|
|
3903
|
-
el.querySelectorAll('path,use,rect,circle,line,polyline,ellipse').forEach((p) => {
|
|
3904
|
-
const f = (p.getAttribute('fill') || '').toLowerCase()
|
|
3905
|
-
if (f && f !== 'none' && f !== 'currentcolor') return
|
|
3906
|
-
p.style.setProperty('fill', 'currentColor', 'important')
|
|
3907
|
-
const s = (p.getAttribute('stroke') || '').toLowerCase()
|
|
3908
|
-
if (s && s !== 'none' && s !== 'currentcolor') return
|
|
3909
|
-
p.style.setProperty('stroke', 'currentColor', 'important')
|
|
3910
|
-
})
|
|
3911
|
-
})
|
|
3912
|
-
}
|
|
3913
|
-
// DSH 提问/弹窗卡(M8wy4a_card 等):内联强制不透明底+原生文字色, 压过 DSH 重渲染/透明与白字
|
|
3914
|
-
const fixDialogs = () => {
|
|
3915
|
-
if (!c.enabled) return
|
|
3916
|
-
const dark = dshDark()
|
|
3917
|
-
const bg = (getComputedStyle(document.documentElement).getPropertyValue('--dsw-specific-input-major') || '').trim() || '#e8edf3'
|
|
3918
|
-
const fg = dark ? '#f2f6fa' : '#1a2332'
|
|
3919
|
-
// 只匹配提问/弹窗卡(M8wy4a_card),避免误匹配输入框 uV2eYG_card(含"card")导致 blur 重影
|
|
3920
|
-
document.querySelectorAll('[class*="M8wy4a_card"]').forEach((el) => {
|
|
3921
|
-
const rgb = bg
|
|
3922
|
-
el.style.setProperty('background-color', `color-mix(in srgb, ${rgb} 76%, transparent)`, 'important')
|
|
3923
|
-
el.style.setProperty('color', fg, 'important')
|
|
3924
|
-
el.style.setProperty('backdrop-filter', 'blur(var(--dsh-glass-blur, 20px))')
|
|
3925
|
-
})
|
|
3926
|
-
}
|
|
3927
|
-
// 主题相关的东西统一重算:背景罩 + 输入框。切主题时由 observer/mq 触发。
|
|
3928
|
-
// 写入前先断开观察器,写完再重连,避免自触发造成死循环。
|
|
3929
|
-
// 把非关键的"内容配色/白字"延迟到浏览器空闲时执行, 让页面先渲染出来(优化刷新加载时间)
|
|
3930
|
-
const defer = (fn) => { try { if (window.requestIdleCallback) window.requestIdleCallback(fn, { timeout: 120 }); else setTimeout(fn, 0) } catch (_) { fn() } }
|
|
3931
|
-
const applyTheme = () => {
|
|
3932
|
-
const obs = window.__dshGlassStyleObs
|
|
3933
|
-
if (obs) obs.disconnect()
|
|
3934
|
-
try {
|
|
3935
|
-
const tk = dshDark() ? 'dark' : 'light'
|
|
3936
|
-
root.setAttribute('data-dsh-theme', tk)
|
|
3937
|
-
window.__dshLongModules = c.modules || {}
|
|
3938
|
-
try { localStorage.setItem('dsh-long:modules', JSON.stringify(window.__dshLongModules)) } catch (e) {}
|
|
3939
|
-
const bt = bgTint(tk)
|
|
3940
|
-
const v = `rgba(${hexToRgb(bt.color)},${bt.mask})`
|
|
3941
|
-
root.style.setProperty('--dsh-glass-bg-zone', v)
|
|
3942
|
-
applyInputCard()
|
|
3943
|
-
defer(() => { applyChromeText(); applySessionText(); fixDialogs() })
|
|
3944
|
-
} finally {
|
|
3945
|
-
if (obs) obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
|
|
3946
|
-
}
|
|
3947
|
-
}
|
|
3948
|
-
|
|
3949
|
-
const body = document.body
|
|
3950
|
-
const frame = document.querySelector('#root > div > div') || document.querySelector('#root > div')
|
|
3951
|
-
// 背景图独立层:铺满整页、位于内容之下;背景图模糊/透明度单独可调(不影响内容清晰度)。
|
|
3952
|
-
// 背景罩用 CSS 变量 var(--dsh-glass-bg-zone),随主题切换自动刷新。
|
|
3953
|
-
let bgimg = document.querySelector('.dsh-glass-bgimg')
|
|
3954
|
-
if (!bgimg) { bgimg = document.createElement('div'); bgimg.className = 'dsh-glass-bgimg'; document.body.appendChild(bgimg) }
|
|
3955
|
-
const gzv = root.style.getPropertyValue('--dsh-glass-bg-zone') || 'rgba(26,35,50,0.28)'
|
|
3956
|
-
if (c.enabled && c.bgImage) {
|
|
3957
|
-
bgimg.style.display = 'block'
|
|
3958
|
-
bgimg.style.backgroundImage = `linear-gradient(var(--dsh-glass-bg-zone, ${gzv}), var(--dsh-glass-bg-zone, ${gzv})), url('${c.bgImage}')`
|
|
3959
|
-
bgimg.style.backgroundSize = 'cover'
|
|
3960
|
-
bgimg.style.backgroundPosition = 'center'
|
|
3961
|
-
bgimg.style.backgroundRepeat = 'no-repeat'
|
|
3962
|
-
// 背景图模糊 = 背景图模糊(bgBlur) + 磨砂模糊强度(blur),两者叠加让背景图一起磨砂
|
|
3963
|
-
// 背景图模糊 = 仅背景图模糊(bgBlur);面板磨砂(blur)作用于弹窗/任务面板的 backdrop-filter
|
|
3964
|
-
const bblur = Math.max(0, Math.min(80, Number.isFinite(Number(c.bgBlur)) ? Number(c.bgBlur) : 0))
|
|
3965
|
-
bgimg.style.filter = bblur > 0 ? `blur(${bblur}px)` : 'none'
|
|
3966
|
-
const bop = Math.max(0, Math.min(1, Number.isFinite(Number(c.bgOpacity)) ? Number(c.bgOpacity) : 1))
|
|
3967
|
-
bgimg.style.opacity = String(bop)
|
|
3968
|
-
} else {
|
|
3969
|
-
bgimg.style.display = 'none'
|
|
3970
|
-
}
|
|
3971
|
-
// 移除 body/frame 上的锐利背景图(改由独立背景层显示),避免重复/遮盖
|
|
3972
|
-
body.style.backgroundImage = 'none'
|
|
3973
|
-
if (c.enabled && c.bgImage) { body.style.backgroundColor = 'transparent' } else { body.style.backgroundColor = '' }
|
|
3974
|
-
if (frame) { frame.style.backgroundImage = 'none'; frame.style.backgroundColor = 'transparent' }
|
|
3975
|
-
|
|
3976
|
-
applyTheme()
|
|
3977
|
-
// 记录当前主题应用函数,供唯一观察器/mq 调用
|
|
3978
|
-
window.__dshGlassApplyTheme = applyTheme
|
|
3979
|
-
centerScroll()
|
|
3980
|
-
// 侧边栏/顶栏 DOM 变化(会话列表更新等)时重算左栏/顶栏文字,防抖 120ms
|
|
3981
|
-
let chromeTimer = null
|
|
3982
|
-
const chromeRoots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
|
|
3983
|
-
const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText, 30) })
|
|
3984
|
-
chromeRoots.forEach((r) => chromeObs.observe(r, { childList: true, subtree: true, characterData: true }))
|
|
3985
|
-
// 输入框卡片(uV2eYG_card/root)可能在启动后才挂载(会话/输入区加载晚),
|
|
3986
|
-
// 启动那一刻 applyInputCard 常找不到框 → 需刷新才生效。监听新增卡片节点时重算,防抖 100ms。
|
|
3987
|
-
let cardTimer = null
|
|
3988
|
-
const hasInputNode = (node) => (node.querySelector && (node.querySelector('[class*="uV2eYG_card"]') || node.querySelector('[class*="uV2eYG_root"]'))) || (node.matches && (node.matches('[class*="uV2eYG_card"]') || node.matches('[class*="uV2eYG_root"]')))
|
|
3989
|
-
const cardObs = new MutationObserver((muts) => {
|
|
3990
|
-
let hit = false
|
|
3991
|
-
for (const m of muts) { for (const node of m.addedNodes) { if (node && node.nodeType === 1 && hasInputNode(node)) { hit = true; break } } if (hit) break }
|
|
3992
|
-
if (hit) { if (cardTimer) clearTimeout(cardTimer); cardTimer = setTimeout(() => applyTheme(), 100) }
|
|
3993
|
-
})
|
|
3994
|
-
cardObs.observe(document.body, { childList: true, subtree: true })
|
|
3995
|
-
// 会话区内容随流式更新会持续变,重算会话区文字(防抖 150ms, 只监听新增/移除节点)
|
|
3996
|
-
let sessionTimer = null
|
|
3997
|
-
const inScroll = (node) => { let p = node; while (p && p !== document.documentElement) { if (p.matches && p.matches('[class*="wSkVaW_scrollBody"]')) return true; p = p.parentElement } return false }
|
|
3998
|
-
const sessionObs = new MutationObserver((muts) => {
|
|
3999
|
-
let hit = false
|
|
4000
|
-
for (const m of muts) { for (const node of m.addedNodes) { if (node.nodeType === 1 && inScroll(node)) { hit = true; break } } if (hit) break }
|
|
4001
|
-
if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() }, 20) }
|
|
4002
|
-
})
|
|
4003
|
-
sessionObs.observe(document.body, { childList: true, subtree: true })
|
|
4004
|
-
}
|
|
4005
|
-
return () => {
|
|
4006
|
-
window.__dshGlassApply = undefined
|
|
4007
|
-
window.removeEventListener('resize', centerScroll)
|
|
4008
|
-
window.__dshGlassCenter = undefined
|
|
4009
|
-
if (window.__dshGlassStyleObs) { window.__dshGlassStyleObs.disconnect(); window.__dshGlassStyleObs = null }
|
|
4010
|
-
if (themeMq) themeMq.removeEventListener('change', themeMqHandler)
|
|
4011
|
-
window.__dshGlassApplyTheme = undefined
|
|
4012
|
-
if (chromeObs) chromeObs.disconnect()
|
|
4013
|
-
if (chromeTimer) clearTimeout(chromeTimer)
|
|
4014
|
-
if (cardObs) cardObs.disconnect()
|
|
4015
|
-
if (cardTimer) clearTimeout(cardTimer)
|
|
4016
|
-
if (sessionObs) sessionObs.disconnect()
|
|
4017
|
-
if (sessionTimer) clearTimeout(sessionTimer)
|
|
4018
|
-
}
|
|
4019
|
-
}, 'dsh-long-plugins: glass applier')
|
|
4020
|
-
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: 'RA-Span' }, GlassSettingsSection))
|
|
4021
|
-
ctx.effect(() => {
|
|
4022
|
-
// 玻璃配置/背景图历史拉取延迟到空闲, 减轻首屏网络风暴(背景没用玻璃时几乎零成本)
|
|
4023
|
-
const loadGlass = () => { fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
|
|
4024
|
-
if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ ...b.cfg, bgImage: b.bgImageUrl || '' }) }
|
|
4025
|
-
}).catch(() => {}) }
|
|
4026
|
-
try { if (window.requestIdleCallback) window.requestIdleCallback(loadGlass, { timeout: 500 }); else setTimeout(loadGlass, 500) } catch (_) { loadGlass() }
|
|
4027
|
-
}, 'dsh-long-plugins: glass boot')
|
|
4028
|
-
},
|
|
4029
|
-
}
|
|
4030
|
-
|
|
4031
3240
|
const inject = Array.from(new Set([
|
|
4032
3241
|
...uploadPlugin.inject,
|
|
4033
3242
|
...skillDocsPlugin.inject,
|
|
4034
3243
|
...tokenUsagePlugin.inject,
|
|
4035
3244
|
...mobilePlugin.inject,
|
|
4036
3245
|
...workspaceFilesPlugin.inject,
|
|
4037
|
-
...glassPlugin.inject,
|
|
4038
3246
|
]))
|
|
4039
3247
|
|
|
4040
3248
|
// 模块开关缓存:页面加载时同步读取(让 main apply 的 modEnabled 门控生效);
|
|
@@ -4055,7 +3263,7 @@ window.__ModuleLoader__.load({
|
|
|
4055
3263
|
console.error('[dsh-long-plugins] ' + label + ' apply failed:', error)
|
|
4056
3264
|
}
|
|
4057
3265
|
}
|
|
4058
|
-
// 「dsh-long」设置区(模块开关+补丁状态)始终注册——独立于任何模块开关,
|
|
3266
|
+
// 「dsh-long」设置区(模块开关+补丁状态)始终注册——独立于任何模块开关, 否则禁用某模块会连带它一起消失
|
|
4059
3267
|
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dsh-long', order: 60, label: 'dsh-long' }, DshLongSettingsSection))
|
|
4060
3268
|
// .dsh-glass-* 布局类(设置区排版)的 CSS 始终注入(不随 glass 开关丢失; 体积小)
|
|
4061
3269
|
ctx.effect(() => {
|
|
@@ -4071,7 +3279,6 @@ window.__ModuleLoader__.load({
|
|
|
4071
3279
|
safeApply('token-usage', (c) => tokenUsagePlugin.apply(c)) // 余额 chip 开关在其内部; MOBILE_CSS 始终注入
|
|
4072
3280
|
if (modEnabled('mobile')) safeApply('mobile-hamburger', (c) => mobilePlugin.apply(c))
|
|
4073
3281
|
if (modEnabled('workspace')) safeApply('workspace-files', (c) => workspaceFilesPlugin.apply(c))
|
|
4074
|
-
if (modEnabled('glass')) safeApply('glass', (c) => glassPlugin.apply(c))
|
|
4075
3282
|
}
|
|
4076
3283
|
|
|
4077
3284
|
exports.apply = apply
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
package/dsh.plugin.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
3
|
"description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, Markdown-to-Word (md2docx), and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Markdown 转 Word(md2docx)、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.6.1",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -718,67 +718,6 @@ async function officePreviewHtml(name, buffer) {
|
|
|
718
718
|
}
|
|
719
719
|
|
|
720
720
|
/** 仅保留毛玻璃配置允许字段并 clamp,避免写入任意/超大内容。背景图只收 data:image/*;base64 且限 2MB。 */
|
|
721
|
-
function sanitizeGlass(input) {
|
|
722
|
-
const out = {};
|
|
723
|
-
if ("enabled" in input) out.enabled = input.enabled === true;
|
|
724
|
-
if ("blur" in input) { const n = Number(input.blur); out.blur = Number.isFinite(n) ? Math.max(0, Math.min(80, Math.round(n))) : undefined; }
|
|
725
|
-
if ("mask" in input) { const n = Number(input.mask); out.mask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : undefined; }
|
|
726
|
-
if ("color" in input) out.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(input.color)) ? String(input.color) : undefined;
|
|
727
|
-
const sanitizeUri = (v) => { const s = String(v ?? ""); return (/^data:image\/(png|jpe?g|webp|gif);base64,/.test(s) && s.length <= 2 * 1024 * 1024 + 256) ? s : undefined; };
|
|
728
|
-
if ("bgImage" in input) out.bgImage = sanitizeUri(input.bgImage);
|
|
729
|
-
if ("bgColor" in input) out.bgColor = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(input.bgColor)) ? String(input.bgColor) : undefined;
|
|
730
|
-
if ("bgMask" in input) { const n = Number(input.bgMask); out.bgMask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : undefined; }
|
|
731
|
-
if ("bgBlur" in input) { const n = Number(input.bgBlur); out.bgBlur = Number.isFinite(n) ? Math.max(0, Math.min(80, n)) : undefined; }
|
|
732
|
-
if ("bgOpacity" in input) { const n = Number(input.bgOpacity); out.bgOpacity = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : undefined; }
|
|
733
|
-
// 背景罩(整页背景图上叠的罩色):浅/深主题各一套 color + mask(罩强度)
|
|
734
|
-
if (input.bgTint && typeof input.bgTint === "object") {
|
|
735
|
-
const bt = {};
|
|
736
|
-
for (const key of ["light", "dark"]) {
|
|
737
|
-
const z = input.bgTint[key];
|
|
738
|
-
if (z && typeof z === "object") {
|
|
739
|
-
const o = {};
|
|
740
|
-
if ("color" in z) o.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(z.color)) ? String(z.color) : "#1a2332";
|
|
741
|
-
if ("mask" in z) { const n = Number(z.mask); o.mask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : 0.28; }
|
|
742
|
-
bt[key] = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
if (Object.keys(bt).length) out.bgTint = bt;
|
|
746
|
-
}
|
|
747
|
-
// 输入框(独立于 zone.input 的 DSH 玻璃变量):浅/深主题各一套 custom color + opacity(1=实底)。color 空串=用主题原生色
|
|
748
|
-
if (input.inputBox && typeof input.inputBox === "object") {
|
|
749
|
-
const ib = {};
|
|
750
|
-
for (const key of ["light", "dark"]) {
|
|
751
|
-
const z = input.inputBox[key];
|
|
752
|
-
if (z && typeof z === "object") {
|
|
753
|
-
const o = {};
|
|
754
|
-
const c = z.color;
|
|
755
|
-
if (c === "" || c === null || c === undefined) o.color = "";
|
|
756
|
-
else o.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(c)) ? String(c) : "";
|
|
757
|
-
if ("opacity" in z) { const n = Number(z.opacity); o.opacity = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : 1; }
|
|
758
|
-
ib[key] = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
if (Object.keys(ib).length) out.inputBox = ib;
|
|
762
|
-
}
|
|
763
|
-
// 会话区/输入区各自独立罩色+罩强度
|
|
764
|
-
if (input.zone && typeof input.zone === "object") {
|
|
765
|
-
const zo = {};
|
|
766
|
-
for (const key of ["session", "input"]) {
|
|
767
|
-
const z = input.zone[key];
|
|
768
|
-
if (z && typeof z === "object") {
|
|
769
|
-
const o = {};
|
|
770
|
-
if ("enabled" in z) o.enabled = z.enabled === true;
|
|
771
|
-
if ("color" in z) o.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(z.color)) ? String(z.color) : undefined;
|
|
772
|
-
if ("mask" in z) { const n = Number(z.mask); o.mask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : undefined; }
|
|
773
|
-
if ("opacity" in z) { const n = Number(z.opacity); o.opacity = Number.isFinite(n) ? Math.max(0, Math.min(1, n)) : undefined; }
|
|
774
|
-
zo[key] = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
if (Object.keys(zo).length) out.zone = zo;
|
|
778
|
-
}
|
|
779
|
-
return Object.fromEntries(Object.entries(out).filter(([, v]) => v !== undefined));
|
|
780
|
-
}
|
|
781
|
-
|
|
782
721
|
export function createHandlers(options = {}) {
|
|
783
722
|
const root = resolve(options.root || resolveUploadRoot());
|
|
784
723
|
const maxFileBytes = positiveInteger(options.maxFileBytes, resolveMaxFileBytes());
|
|
@@ -1676,160 +1615,6 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1676
1615
|
}
|
|
1677
1616
|
};
|
|
1678
1617
|
|
|
1679
|
-
// ---- 毛玻璃界面 (glass UI) 配置:读写 ~/.dsh-long-plugins/glass.json ----
|
|
1680
|
-
// 背景图以 data-URI 存进配置,随配置持久化。enabled=总开关;blur=磨砂模糊;mask=整页罩强度;
|
|
1681
|
-
// color=叠加/罩色;bgImage=共用背景图。顶栏/左栏的"罩"与整页共用 mask+color(不再单独存,简化)。
|
|
1682
|
-
const GLASS_DEFAULTS = {
|
|
1683
|
-
enabled: false, blur: 20, bgImage: "", bgColor: "#1a2332", bgMask: 0.28,
|
|
1684
|
-
bgTint: { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
|
|
1685
|
-
inputBox: { light: { color: "", opacity: 1 }, dark: { color: "", opacity: 1 } },
|
|
1686
|
-
zone: { session: { color: "#1a2332", mask: 0.45, opacity: 0.5 }, input: { color: "#1a2332", mask: 0.6, opacity: 0.55 } },
|
|
1687
|
-
// 各模块开关(「dsh-long」设置区):false=禁用该模块功能(避免与 DSH 新版自带功能冲突/按需关闭)
|
|
1688
|
-
modules: { glass: false, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: false },
|
|
1689
|
-
};
|
|
1690
|
-
const glassDir = resolve(homedir(), ".dsh-long-plugins");
|
|
1691
|
-
const glassFile = join(glassDir, "glass.json");
|
|
1692
|
-
const bgDir = join(glassDir, "backgrounds");
|
|
1693
|
-
const currentBgFile = join(bgDir, "current.uri");
|
|
1694
|
-
// 背景图文件引用: 配置里 bgImage 为 "current"(存 bgDir/current.uri) 或 ""(无) ;不再内嵌大图 data-URI
|
|
1695
|
-
const decodeDataUri = (s) => { const m = /^data:(image\/[a-zA-Z0-9.+-]+);base64,([A-Za-z0-9+/=]+)$/.exec(String(s || "")); return m ? { mime: m[1], base64: m[2] } : null; };
|
|
1696
|
-
const bgImageUrl = (cfg) => (cfg.bgImage === "current") ? "/api/dsh-uploads/glass-background" : "";
|
|
1697
|
-
// 只保留当前背景图(current.uri), 删除其它历史图片文件(旧图清理)
|
|
1698
|
-
async function pruneBackgrounds() {
|
|
1699
|
-
try {
|
|
1700
|
-
const names = await readdir(bgDir);
|
|
1701
|
-
for (const n of names) {
|
|
1702
|
-
if (n === "current.uri" || !n.endsWith(".uri")) continue;
|
|
1703
|
-
try { await unlink(join(bgDir, n)); } catch (_) {}
|
|
1704
|
-
}
|
|
1705
|
-
} catch (_) {}
|
|
1706
|
-
}
|
|
1707
|
-
async function readGlassJSON() {
|
|
1708
|
-
try {
|
|
1709
|
-
const raw = JSON.parse(await readFile(glassFile, "utf8"));
|
|
1710
|
-
const cfg = { ...GLASS_DEFAULTS, ...(raw && typeof raw === "object" ? raw : {}) };
|
|
1711
|
-
// 迁移旧版: bgImage 是内嵌 data-URI(大图) → 存成文件 + 改引用
|
|
1712
|
-
if (typeof cfg.bgImage === "string" && cfg.bgImage.startsWith("data:image/")) {
|
|
1713
|
-
try {
|
|
1714
|
-
await mkdir(bgDir, { recursive: true, mode: 0o700 });
|
|
1715
|
-
await writeFile(currentBgFile, cfg.bgImage, "utf8");
|
|
1716
|
-
cfg.bgImage = "current";
|
|
1717
|
-
await writeFile(glassFile, JSON.stringify(cfg, null, 2), "utf8");
|
|
1718
|
-
} catch (_) {}
|
|
1719
|
-
}
|
|
1720
|
-
// 插件升级时:版本号变化 → 默认关闭「会话导航」「RA-Span」(避免与 DSH 新版自带功能冲突)
|
|
1721
|
-
if (PLUGIN_VERSION && cfg._pluginVersion !== PLUGIN_VERSION) {
|
|
1722
|
-
try {
|
|
1723
|
-
cfg.modules = { glass: false, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: false, ...(cfg.modules || {}) };
|
|
1724
|
-
cfg.modules.glass = false;
|
|
1725
|
-
cfg.modules.turnRuler = false;
|
|
1726
|
-
cfg._pluginVersion = PLUGIN_VERSION;
|
|
1727
|
-
await writeFile(glassFile, JSON.stringify(cfg, null, 2), "utf8");
|
|
1728
|
-
} catch (_) {}
|
|
1729
|
-
}
|
|
1730
|
-
return cfg;
|
|
1731
|
-
} catch { return { ...GLASS_DEFAULTS }; }
|
|
1732
|
-
}
|
|
1733
|
-
const glassConfig = async (req, res) => {
|
|
1734
|
-
try {
|
|
1735
|
-
requireTrusted(req);
|
|
1736
|
-
if (req.method === "GET" || req.method === "HEAD") {
|
|
1737
|
-
const cfg = await readGlassJSON();
|
|
1738
|
-
// 背景图只返回"是否存在 + 图片 URL", 不再返回内嵌大图 data-URI(减小响应)
|
|
1739
|
-
let imgMeta = { present: false, bytes: 0 };
|
|
1740
|
-
let imgUrl = "";
|
|
1741
|
-
if (cfg.bgImage === "current") {
|
|
1742
|
-
imgUrl = "/api/dsh-uploads/glass-background";
|
|
1743
|
-
try { imgMeta = { present: true, bytes: (await stat(currentBgFile)).size || 0 }; } catch (_) { imgMeta = { present: true, bytes: 0 }; }
|
|
1744
|
-
}
|
|
1745
|
-
sendJson(res, 200, {
|
|
1746
|
-
found: true,
|
|
1747
|
-
cfg: {
|
|
1748
|
-
enabled: cfg.enabled, blur: cfg.blur,
|
|
1749
|
-
session: cfg.zone?.session || { enabled: true, color: "#1a2332", mask: 0.45, opacity: 0.5 },
|
|
1750
|
-
input: cfg.zone?.input || { color: "#1a2332", mask: 0.6, opacity: 0.55 },
|
|
1751
|
-
inputBox: cfg.inputBox || { light: { color: "", opacity: 1 }, dark: { color: "", opacity: 1 } },
|
|
1752
|
-
bgTint: cfg.bgTint || { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
|
|
1753
|
-
bgColor: cfg.bgColor || "#1a2332", bgMask: cfg.bgMask ?? 0.28,
|
|
1754
|
-
bgBlur: cfg.bgBlur ?? 0, bgOpacity: cfg.bgOpacity ?? 1,
|
|
1755
|
-
turnRuler: (cfg.modules && cfg.modules.turnRuler) !== false,
|
|
1756
|
-
modules: { glass: false, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: false, ...(cfg.modules || {}) },
|
|
1757
|
-
bgImage: imgMeta,
|
|
1758
|
-
},
|
|
1759
|
-
bgImage: imgMeta,
|
|
1760
|
-
bgImageUrl: imgUrl,
|
|
1761
|
-
});
|
|
1762
|
-
return;
|
|
1763
|
-
}
|
|
1764
|
-
if (req.method === "POST") {
|
|
1765
|
-
const body = await readJsonBody(req, 4 * 1024 * 1024);
|
|
1766
|
-
const current = await readGlassJSON();
|
|
1767
|
-
const next = { ...current, ...sanitizeGlass(typeof body === "object" && body !== null ? body : {}) };
|
|
1768
|
-
// 各模块开关表:body.modules 里有的键按布尔合并
|
|
1769
|
-
if (body && typeof body.modules === "object" && body.modules !== null) {
|
|
1770
|
-
const m = { glass: true, uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: true, ...(current.modules || {}) };
|
|
1771
|
-
for (const k of Object.keys(body.modules)) m[k] = body.modules[k] !== false;
|
|
1772
|
-
next.modules = m;
|
|
1773
|
-
}
|
|
1774
|
-
// 背景图改为"文件 + 引用": 用户上传的 data-URI 存入 current.uri, 配置只存 "current" 引用
|
|
1775
|
-
if (typeof next.bgImage === "string" && next.bgImage.startsWith("data:image/")) {
|
|
1776
|
-
try {
|
|
1777
|
-
await mkdir(bgDir, { recursive: true, mode: 0o700 });
|
|
1778
|
-
await writeFile(currentBgFile, next.bgImage, "utf8");
|
|
1779
|
-
next.bgImage = "current";
|
|
1780
|
-
} catch (_) { next.bgImage = ""; }
|
|
1781
|
-
} else if ("bgImage" in body && body.bgImage === "") {
|
|
1782
|
-
next.bgImage = ""; // 显式清除
|
|
1783
|
-
try { await unlink(currentBgFile); } catch (_) {}
|
|
1784
|
-
}
|
|
1785
|
-
// 只保留当前背景图, 删除历史旧图
|
|
1786
|
-
await pruneBackgrounds();
|
|
1787
|
-
await mkdir(glassDir, { recursive: true, mode: 0o700 });
|
|
1788
|
-
await writeFile(glassFile, JSON.stringify(next, null, 2), "utf8");
|
|
1789
|
-
sendJson(res, 200, { ok: true, saved: true });
|
|
1790
|
-
return;
|
|
1791
|
-
}
|
|
1792
|
-
methodNotAllowed(res, ["GET", "HEAD", "POST"]);
|
|
1793
|
-
} catch (error) {
|
|
1794
|
-
sendError(res, error, onError);
|
|
1795
|
-
}
|
|
1796
|
-
};
|
|
1797
|
-
// 背景图历史(供设置面板"选择旧图"):GET 列出,GET ?name= 返回对应 data-URI
|
|
1798
|
-
const glassBackgrounds = async (req, res) => {
|
|
1799
|
-
try {
|
|
1800
|
-
requireTrusted(req);
|
|
1801
|
-
const url = new URL(req.url, "http://x");
|
|
1802
|
-
const name = url.searchParams.get("name");
|
|
1803
|
-
if (name) {
|
|
1804
|
-
if (!/^[\w.:-]+\.uri$/.test(name)) { sendError(res, new Error("bad name"), onError); return; }
|
|
1805
|
-
try { const buf = await readFile(join(bgDir, name)); res.writeHead(200, { "content-type": "text/plain; charset=utf-8", "cache-control": "no-store" }); res.end(buf); return; }
|
|
1806
|
-
catch { sendJson(res, 404, { error: "not found" }); return; }
|
|
1807
|
-
}
|
|
1808
|
-
let list = [];
|
|
1809
|
-
// 只保留当前背景图, 删除历史旧图; 列表排除当前图
|
|
1810
|
-
try { await pruneBackgrounds(); } catch (_) {}
|
|
1811
|
-
try { list = (await readdir(bgDir)).filter((n) => n.endsWith(".uri") && n !== "current.uri").sort().reverse().slice(0, 30); } catch (_) {}
|
|
1812
|
-
sendJson(res, 200, { list });
|
|
1813
|
-
} catch (error) {
|
|
1814
|
-
sendError(res, error, onError);
|
|
1815
|
-
}
|
|
1816
|
-
};
|
|
1817
|
-
// 返回当前背景图(文件存的 data-URI 解码为真实图片)
|
|
1818
|
-
const glassBackground = async (req, res) => {
|
|
1819
|
-
try {
|
|
1820
|
-
requireTrusted(req);
|
|
1821
|
-
const cfg = await readGlassJSON();
|
|
1822
|
-
if (cfg.bgImage !== "current") { sendJson(res, 404, { error: "no background" }); return; }
|
|
1823
|
-
const buf = await readFile(currentBgFile, "utf8");
|
|
1824
|
-
const d = decodeDataUri(buf);
|
|
1825
|
-
if (!d) { sendJson(res, 404, { error: "bad image" }); return; }
|
|
1826
|
-
const bytes = Buffer.from(d.base64, "base64");
|
|
1827
|
-
res.writeHead(200, { "content-type": d.mime, "cache-control": "no-store", "x-content-type-options": "nosniff" });
|
|
1828
|
-
res.end(bytes);
|
|
1829
|
-
} catch (error) {
|
|
1830
|
-
sendError(res, error, onError);
|
|
1831
|
-
}
|
|
1832
|
-
};
|
|
1833
1618
|
// 补丁状态:只读检测 DSH 核心各补丁的「已打 / 未打 / 原生无需」,供 dsh-long 设置区显示
|
|
1834
1619
|
const patchStatus = async (req, res) => {
|
|
1835
1620
|
try {
|
|
@@ -1863,7 +1648,42 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1863
1648
|
}
|
|
1864
1649
|
};
|
|
1865
1650
|
|
|
1866
|
-
|
|
1651
|
+
// 各模块开关(dsh-long 设置区):读写 ~/.dsh-long-plugins/modules.json(独立于 dsh-span 的 RA-Span 配置)。
|
|
1652
|
+
const MODULES_DEFAULTS = { uploadAttach: true, uploadDragDrop: true, uploadPaste: true, uploadPreview: true, skillDocs: true, balance: true, mobile: true, workspace: true, turnRuler: false };
|
|
1653
|
+
const modulesFile = join(homedir(), ".dsh-long-plugins", "modules.json");
|
|
1654
|
+
async function readModulesJSON() {
|
|
1655
|
+
try {
|
|
1656
|
+
const raw = JSON.parse(await readFile(modulesFile, "utf8"));
|
|
1657
|
+
return { ...MODULES_DEFAULTS, ...(raw && typeof raw === "object" && raw.modules ? raw.modules : {}) };
|
|
1658
|
+
} catch { return { ...MODULES_DEFAULTS }; }
|
|
1659
|
+
}
|
|
1660
|
+
const modulesConfig = async (req, res) => {
|
|
1661
|
+
try {
|
|
1662
|
+
requireTrusted(req);
|
|
1663
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
1664
|
+
const modules = await readModulesJSON();
|
|
1665
|
+
sendJson(res, 200, { ok: true, cfg: { modules } });
|
|
1666
|
+
return;
|
|
1667
|
+
}
|
|
1668
|
+
if (req.method === "POST") {
|
|
1669
|
+
const body = await readJsonBody(req, 1024 * 1024);
|
|
1670
|
+
const current = await readModulesJSON();
|
|
1671
|
+
const next = { ...current };
|
|
1672
|
+
if (body && typeof body.modules === "object" && body.modules !== null) {
|
|
1673
|
+
for (const k of Object.keys(body.modules)) next[k] = body.modules[k] !== false;
|
|
1674
|
+
}
|
|
1675
|
+
await mkdir(join(homedir(), ".dsh-long-plugins"), { recursive: true, mode: 0o700 });
|
|
1676
|
+
await writeFile(modulesFile, JSON.stringify({ modules: next }, null, 2), "utf8");
|
|
1677
|
+
sendJson(res, 200, { ok: true, saved: true });
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
methodNotAllowed(res, ["GET", "HEAD", "POST"]);
|
|
1681
|
+
} catch (error) {
|
|
1682
|
+
sendError(res, error, onError);
|
|
1683
|
+
}
|
|
1684
|
+
};
|
|
1685
|
+
|
|
1686
|
+
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, patchStatus, modulesConfig };
|
|
1867
1687
|
}
|
|
1868
1688
|
|
|
1869
1689
|
/** 人类可读文件大小。 */
|
|
@@ -2724,29 +2544,16 @@ export async function apply(ctx, config = {}) {
|
|
|
2724
2544
|
}), "dsh-long-plugins: xlsx-preview vendor assets");
|
|
2725
2545
|
|
|
2726
2546
|
ctx.effect(() => ctx.webServer.register({
|
|
2727
|
-
kind: "exact",
|
|
2728
|
-
path: "/api/dsh-uploads/glass-config",
|
|
2729
|
-
handler: handlers.glassConfig,
|
|
2730
|
-
}), "dsh-long-plugins: glass-ui config route");
|
|
2731
|
-
|
|
2732
|
-
ctx.effect(() => ctx.webServer.register({
|
|
2733
|
-
kind: "exact",
|
|
2734
|
-
path: "/api/dsh-uploads/glass-backgrounds",
|
|
2735
|
-
handler: handlers.glassBackgrounds,
|
|
2736
|
-
}), "dsh-long-plugins: glass-ui backgrounds route");
|
|
2737
|
-
|
|
2738
|
-
ctx.effect(() => ctx.webServer.register({
|
|
2739
|
-
kind: "exact",
|
|
2740
|
-
path: "/api/dsh-uploads/glass-background",
|
|
2741
|
-
handler: handlers.glassBackground,
|
|
2742
|
-
}), "dsh-long-plugins: glass-ui background image route");
|
|
2743
|
-
|
|
2744
|
-
ctx.effect(() => ctx.webServer.register({
|
|
2745
|
-
kind: "exact",
|
|
2746
2547
|
path: "/api/dsh-uploads/patch-status",
|
|
2747
2548
|
handler: handlers.patchStatus,
|
|
2748
2549
|
}), "dsh-long-plugins: patch status route");
|
|
2749
2550
|
|
|
2551
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2552
|
+
kind: "exact",
|
|
2553
|
+
path: "/api/dsh-uploads/modules-config",
|
|
2554
|
+
handler: handlers.modulesConfig,
|
|
2555
|
+
}), "dsh-long-plugins: modules config route");
|
|
2556
|
+
|
|
2750
2557
|
ctx.effect(() => ctx.webServer.register({
|
|
2751
2558
|
kind: "exact",
|
|
2752
2559
|
path: "/api/dsh-uploads/workspace-browse",
|
|
@@ -2971,13 +2778,25 @@ export async function apply(ctx, config = {}) {
|
|
|
2971
2778
|
}
|
|
2972
2779
|
const session = ctx.sessions?.get(sessionId);
|
|
2973
2780
|
let events;
|
|
2974
|
-
if (session !== undefined) {
|
|
2781
|
+
if (session !== undefined && typeof session.snapshotEvents === "function") {
|
|
2782
|
+
// DSH 0.1.2-alpha.5 Session exposes snapshotEvents() (the the .events
|
|
2783
|
+
// property is not part of the live Session surface). The legacy
|
|
2784
|
+
// .events array is kept as a fallback for older DSH builds.
|
|
2785
|
+
events = session.snapshotEvents();
|
|
2786
|
+
} else if (session !== undefined && Array.isArray(session.events)) {
|
|
2975
2787
|
events = session.events;
|
|
2976
2788
|
} else if (ctx.sessionPersistence !== undefined) {
|
|
2977
2789
|
// Cold (historical) session: restore through the durable persistence
|
|
2978
2790
|
// backend. inspect() prefers the live store and falls back to disk.
|
|
2979
|
-
|
|
2980
|
-
|
|
2791
|
+
// It throws for a session whose live turn is still open; treat that
|
|
2792
|
+
// as "no usable events" instead of a 500 so the chip degrades to
|
|
2793
|
+
// balance-only rather than erroring.
|
|
2794
|
+
try {
|
|
2795
|
+
const inspected = await ctx.sessionPersistence.inspect(sessionId);
|
|
2796
|
+
events = inspected && Array.isArray(inspected.events) ? inspected.events : undefined;
|
|
2797
|
+
} catch {
|
|
2798
|
+
events = undefined;
|
|
2799
|
+
}
|
|
2981
2800
|
}
|
|
2982
2801
|
if (events === undefined) {
|
|
2983
2802
|
sendJson(res, 404, { ok: false, error: "session not found" });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。Office 读取/生成已独立到 dsh-office-reader。",
|
|
3
|
+
"version": "2.6.1",
|
|
4
|
+
"description": "Merged DSH web plugins: upload manager (drag-and-drop attach any file to the message), workspace output files, skill docs, account balance, and polished file preview (Word & PowerPoint real preview with zoom/pan, rendered Markdown), plus an auto-repair install for DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink). | 一个插件整合 DSH Web 的常用增强:上传管理(拖放上传:拖任意文件到会话框直接附加)、工作区「输出文件」面板、技能文档浏览、账户余额显示、Word/PowerPoint 真实预览(可缩放、翻页);并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。Office 读取/生成已独立到 dsh-office-reader;RA-Span 已独立到 dsh-span。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"main": "lib/index.js",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|