dsh-long-plugins 2.2.3 → 2.3.0
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 +66 -33
- package/dsh.plugin.json +1 -1
- package/lib/index.js +76 -13
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -1961,7 +1961,9 @@ window.__ModuleLoader__.load({
|
|
|
1961
1961
|
if (!cancelled) setBalance(null);
|
|
1962
1962
|
}
|
|
1963
1963
|
};
|
|
1964
|
-
|
|
1964
|
+
// 首次拉取延迟到浏览器空闲(减轻首屏网络风暴), 60s 轮询不变
|
|
1965
|
+
const schedule = () => { try { if (window.requestIdleCallback) window.requestIdleCallback(() => load(), { timeout: 300 }); else setTimeout(load, 300) } catch (_) { load() } };
|
|
1966
|
+
schedule();
|
|
1965
1967
|
const interval = setInterval(load, 60000);
|
|
1966
1968
|
return () => {
|
|
1967
1969
|
cancelled = true;
|
|
@@ -2003,7 +2005,9 @@ window.__ModuleLoader__.load({
|
|
|
2003
2005
|
if (!cancelled) setCost(null);
|
|
2004
2006
|
}
|
|
2005
2007
|
};
|
|
2006
|
-
|
|
2008
|
+
// 首次拉取延迟到空闲(减轻首屏网络风暴), 60s 轮询不变
|
|
2009
|
+
const schedule = () => { try { if (window.requestIdleCallback) window.requestIdleCallback(() => load(), { timeout: 300 }); else setTimeout(load, 300) } catch (_) { load() } };
|
|
2010
|
+
schedule();
|
|
2007
2011
|
const interval = setInterval(load, 60000);
|
|
2008
2012
|
return () => {
|
|
2009
2013
|
cancelled = true;
|
|
@@ -3133,6 +3137,7 @@ window.__ModuleLoader__.load({
|
|
|
3133
3137
|
color: #ffffff !important;
|
|
3134
3138
|
text-shadow: 0 1px 1px rgba(0,0,0,.5);
|
|
3135
3139
|
}
|
|
3140
|
+
/* (移除 color/text-shadow 过渡: 会在收尾重渲染时短暂贴默认色导致"闪黑") */
|
|
3136
3141
|
/* DSH 弹窗/浮层(提问/确认/重命名等):给不透明底+主题文字, 杜绝透明与会话内容重叠 */
|
|
3137
3142
|
html[data-dsh-glass="on"] [class*="Radix"] [class*="Content"],
|
|
3138
3143
|
html[data-dsh-glass="on"] [class*="DialogContent"],
|
|
@@ -3165,6 +3170,19 @@ window.__ModuleLoader__.load({
|
|
|
3165
3170
|
html[data-dsh-glass="on"] [class*="lXshSW_item"] {
|
|
3166
3171
|
color: var(--dsw-alias-label-primary) !important;
|
|
3167
3172
|
}
|
|
3173
|
+
/* 排队消息/等待任务面板(_7yHdaG_dock/panel, 在 conversation.input.dock 内):不透明/磨砂底+可读文字 */
|
|
3174
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_panel"],
|
|
3175
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_dock"] {
|
|
3176
|
+
background-color: color-mix(in srgb, var(--dsw-specific-input-major, #e8edf3) 88%, transparent) !important;
|
|
3177
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3178
|
+
backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3179
|
+
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px)) !important;
|
|
3180
|
+
}
|
|
3181
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_header"],
|
|
3182
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_preview"],
|
|
3183
|
+
html[data-dsh-glass="on"] [class*="_7yHdaG_row"] {
|
|
3184
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3185
|
+
}
|
|
3168
3186
|
/* 轮次/历史提问预览:默认隐藏(点开才显示),并去掉头部底色,避免底部露出白条 */
|
|
3169
3187
|
.dsh-turn-preview:not(.open){display:none !important;}
|
|
3170
3188
|
.dsh-turn-preview-head{background:transparent !important;}
|
|
@@ -3224,18 +3242,27 @@ window.__ModuleLoader__.load({
|
|
|
3224
3242
|
backdrop-filter: none !important;
|
|
3225
3243
|
-webkit-backdrop-filter: none !important;
|
|
3226
3244
|
}
|
|
3227
|
-
/* 输入框旁权限/功能选择下拉(盾牌 Full access 等
|
|
3228
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"]
|
|
3229
|
-
|
|
3230
|
-
|
|
3245
|
+
/* 输入框旁权限/功能选择下拉(盾牌 Full access 等):给不透明底+可读文字(高优先级, 压过 DSH 透明覆盖) */
|
|
3246
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"],
|
|
3247
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_root_"] [class*="_list_"] [class*="_viewport_"],
|
|
3248
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_list_"] [class*="_viewport_"] [class*="_itemWrap_"] {
|
|
3249
|
+
background-color: var(--dsw-specific-input-major, #e8edf3) !important;
|
|
3231
3250
|
backdrop-filter: none !important;
|
|
3232
3251
|
-webkit-backdrop-filter: none !important;
|
|
3233
3252
|
}
|
|
3234
|
-
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="
|
|
3235
|
-
|
|
3236
|
-
|
|
3253
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_item_"],
|
|
3254
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] [class*="_itemLabel_"] {
|
|
3255
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3256
|
+
}
|
|
3257
|
+
/* 兜底: Radix 风格菜单/下拉(可能被渲染到 body 顶层) */
|
|
3258
|
+
html[data-dsh-glass="on"] [class*="Radix"] [class*="Menu"],
|
|
3259
|
+
html[data-dsh-glass="on"] [class*="radix-menu"],
|
|
3260
|
+
html[data-dsh-glass="on"] [class*="menuitem"],
|
|
3261
|
+
html[data-dsh-glass="on"] [role="menu"] {
|
|
3262
|
+
background-color: var(--dsw-specific-input-major, #e8edf3) !important;
|
|
3237
3263
|
backdrop-filter: none !important;
|
|
3238
3264
|
-webkit-backdrop-filter: none !important;
|
|
3265
|
+
color: var(--dsw-alias-label-primary) !important;
|
|
3239
3266
|
}
|
|
3240
3267
|
/* 玻璃开启 + 浅色系统主题:加深弱化标签/元信息文字,避免浅灰字叠在浅磨砂卡上看不清 */
|
|
3241
3268
|
@media (prefers-color-scheme: light) {
|
|
@@ -3329,7 +3356,7 @@ window.__ModuleLoader__.load({
|
|
|
3329
3356
|
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3330
3357
|
let list = []
|
|
3331
3358
|
try { const rb = await fetch('/api/dsh-uploads/glass-backgrounds', { cache: 'no-store' }); const jb = await rb.json(); list = jb.list || [] } catch (_) {}
|
|
3332
|
-
setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.
|
|
3359
|
+
setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.bgImageUrl || '', bgList: list }))
|
|
3333
3360
|
} catch (e) { setState((s) => ({ ...s, loading: false, error: glassText(e) })) }
|
|
3334
3361
|
}, [])
|
|
3335
3362
|
React.useEffect(() => { load(); }, [load])
|
|
@@ -3352,9 +3379,16 @@ window.__ModuleLoader__.load({
|
|
|
3352
3379
|
try {
|
|
3353
3380
|
const cfg = state.cfg || {}
|
|
3354
3381
|
const num = (v, fb) => ((typeof v === 'number' && Number.isFinite(v) && v >= 0) ? v : fb)
|
|
3382
|
+
// 背景图: 仅当用户上传了新图(data-URI)才传; 未改(URL)省略以保持引用; 清空传 '' 清除
|
|
3383
|
+
const sBg = state.bgImage
|
|
3384
|
+
let bgImageOut
|
|
3385
|
+
if (sBg === '') bgImageOut = ''
|
|
3386
|
+
else if (typeof sBg === 'string' && sBg.startsWith('data:image/')) bgImageOut = sBg
|
|
3387
|
+
else bgImageOut = undefined
|
|
3355
3388
|
const payload = {
|
|
3356
3389
|
enabled: !!cfg.enabled, blur: num(cfg.blur, 0),
|
|
3357
|
-
|
|
3390
|
+
bgMask: num(cfg.bgMask, 0.28), bgColor: cfg.bgColor || '#1a2332', bgBlur: num(cfg.bgBlur, 0), bgOpacity: num(cfg.bgOpacity, 1),
|
|
3391
|
+
...(bgImageOut !== undefined ? { bgImage: bgImageOut } : {}),
|
|
3358
3392
|
bgTint: {
|
|
3359
3393
|
light: { color: (cfg.bgTint?.light?.color) || '#1a2332', mask: num(cfg.bgTint?.light?.mask, 0.28) },
|
|
3360
3394
|
dark: { color: (cfg.bgTint?.dark?.color) || '#1a2332', mask: num(cfg.bgTint?.dark?.mask, 0.28) },
|
|
@@ -3371,8 +3405,9 @@ window.__ModuleLoader__.load({
|
|
|
3371
3405
|
const r = await fetch('/api/dsh-uploads/glass-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
|
|
3372
3406
|
const b = await r.json()
|
|
3373
3407
|
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3374
|
-
|
|
3375
|
-
|
|
3408
|
+
const applyBg = (payload.bgImage && payload.bgImage.startsWith('data:')) ? payload.bgImage : ((state.bgImage && state.bgImage.startsWith('/')) ? state.bgImage : '')
|
|
3409
|
+
window.__dshGlassApply && window.__dshGlassApply({ ...payload, bgImage: applyBg })
|
|
3410
|
+
setState((s) => ({ ...s, saving: false, cfg: { ...(s.cfg || {}), ...payload }, bgImage: applyBg, error: '' }))
|
|
3376
3411
|
} catch (e) { setState((s) => ({ ...s, saving: false, error: glassText(e) })) }
|
|
3377
3412
|
}, [state])
|
|
3378
3413
|
const cfg = state.cfg || {}
|
|
@@ -3586,18 +3621,15 @@ window.__ModuleLoader__.load({
|
|
|
3586
3621
|
if (isInSkip(root)) return
|
|
3587
3622
|
root.querySelectorAll('div,span,button,a,label,p,li,h1,h2,h3,h4').forEach((el) => {
|
|
3588
3623
|
if (isInSkip(el)) return
|
|
3589
|
-
|
|
3590
|
-
const hasText = el.textContent && el.textContent.trim()
|
|
3591
|
-
if (!hasText) return
|
|
3624
|
+
if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过
|
|
3592
3625
|
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3593
|
-
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(
|
|
3626
|
+
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4)$/.test(el.tagName)
|
|
3594
3627
|
if (!textLike && !ownText) return
|
|
3595
|
-
// 跳过有盒子背景的元素(
|
|
3596
|
-
|
|
3597
|
-
const bgbg = getComputedStyle(el).backgroundColor
|
|
3598
|
-
if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
|
|
3628
|
+
// 跳过有盒子背景的元素(用类名判断, 不再 getComputedStyle 以免每元素强制样式重算导致卡顿)
|
|
3629
|
+
if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain|button/i.test(el.className)) return
|
|
3599
3630
|
el.style.setProperty('color', '#ffffff', 'important')
|
|
3600
3631
|
el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
|
|
3632
|
+
if (el.dataset) el.dataset.dshgl = '1'
|
|
3601
3633
|
})
|
|
3602
3634
|
// 图标也变白(随 currentColor),用于工作区文件夹等单色图标;保留显式品牌色
|
|
3603
3635
|
root.querySelectorAll('svg, [class*="icon"], [class*="Icon"]').forEach((el) => {
|
|
@@ -3622,15 +3654,14 @@ window.__ModuleLoader__.load({
|
|
|
3622
3654
|
if (!scroll) return
|
|
3623
3655
|
scroll.querySelectorAll('div,span,p,li,pre,code,button,a,label,h1,h2,h3,h4').forEach((el) => {
|
|
3624
3656
|
if (isInSkip(el)) return
|
|
3625
|
-
|
|
3626
|
-
if (!hasText) return
|
|
3657
|
+
if (el.dataset && el.dataset.dshgl) return // 已处理过, 跳过(只处理新内容)
|
|
3627
3658
|
const ownText = Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.textContent.trim())
|
|
3628
3659
|
const textLike = /^(BUTTON|A|SPAN|LABEL|LI|P|H1|H2|H3|H4|PRE|CODE)$/.test(el.tagName)
|
|
3629
3660
|
if (!textLike && !ownText) return
|
|
3630
|
-
|
|
3631
|
-
if (bgbg && bgbg !== 'rgba(0, 0, 0, 0)' && bgbg !== 'transparent') return
|
|
3661
|
+
if (/bubble|badge|chip|tag|pill|code-block|md-code|n_block|n_banner|n_plain/i.test(el.className)) return
|
|
3632
3662
|
el.style.setProperty('color', '#ffffff', 'important')
|
|
3633
3663
|
el.style.setProperty('text-shadow', '0 1px 1px rgba(0,0,0,.5)')
|
|
3664
|
+
if (el.dataset) el.dataset.dshgl = '1'
|
|
3634
3665
|
})
|
|
3635
3666
|
// 在白字底上, 给过程内容配色区分, Error 用红色。
|
|
3636
3667
|
// 文字常在标签的子元素里(白字已把它设白), 故连同文本子元素一起上色。
|
|
@@ -3726,6 +3757,8 @@ window.__ModuleLoader__.load({
|
|
|
3726
3757
|
}
|
|
3727
3758
|
// 主题相关的东西统一重算:背景罩 + 输入框。切主题时由 observer/mq 触发。
|
|
3728
3759
|
// 写入前先断开观察器,写完再重连,避免自触发造成死循环。
|
|
3760
|
+
// 把非关键的"内容配色/白字"延迟到浏览器空闲时执行, 让页面先渲染出来(优化刷新加载时间)
|
|
3761
|
+
const defer = (fn) => { try { if (window.requestIdleCallback) window.requestIdleCallback(fn, { timeout: 120 }); else setTimeout(fn, 0) } catch (_) { fn() } }
|
|
3729
3762
|
const applyTheme = () => {
|
|
3730
3763
|
const obs = window.__dshGlassStyleObs
|
|
3731
3764
|
if (obs) obs.disconnect()
|
|
@@ -3736,9 +3769,7 @@ window.__ModuleLoader__.load({
|
|
|
3736
3769
|
const v = `rgba(${hexToRgb(bt.color)},${bt.mask})`
|
|
3737
3770
|
root.style.setProperty('--dsh-glass-bg-zone', v)
|
|
3738
3771
|
applyInputCard()
|
|
3739
|
-
applyChromeText()
|
|
3740
|
-
applySessionText()
|
|
3741
|
-
fixDialogs()
|
|
3772
|
+
defer(() => { applyChromeText(); applySessionText(); fixDialogs() })
|
|
3742
3773
|
} finally {
|
|
3743
3774
|
if (obs) obs.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] })
|
|
3744
3775
|
}
|
|
@@ -3778,7 +3809,7 @@ window.__ModuleLoader__.load({
|
|
|
3778
3809
|
// 侧边栏/顶栏 DOM 变化(会话列表更新等)时重算左栏/顶栏文字,防抖 120ms
|
|
3779
3810
|
let chromeTimer = null
|
|
3780
3811
|
const chromeRoots = [...document.querySelectorAll('#root [class*="sidebarCol"],#root [class*="wSkVaW_header"]')]
|
|
3781
|
-
const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText,
|
|
3812
|
+
const chromeObs = new MutationObserver(() => { if (chromeTimer) clearTimeout(chromeTimer); chromeTimer = setTimeout(applyChromeText, 30) })
|
|
3782
3813
|
chromeRoots.forEach((r) => chromeObs.observe(r, { childList: true, subtree: true, characterData: true }))
|
|
3783
3814
|
// 输入框卡片(uV2eYG_card/root)可能在启动后才挂载(会话/输入区加载晚),
|
|
3784
3815
|
// 启动那一刻 applyInputCard 常找不到框 → 需刷新才生效。监听新增卡片节点时重算,防抖 100ms。
|
|
@@ -3796,7 +3827,7 @@ window.__ModuleLoader__.load({
|
|
|
3796
3827
|
const sessionObs = new MutationObserver((muts) => {
|
|
3797
3828
|
let hit = false
|
|
3798
3829
|
for (const m of muts) { for (const node of m.addedNodes) { if (node.nodeType === 1 && inScroll(node)) { hit = true; break } } if (hit) break }
|
|
3799
|
-
if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() },
|
|
3830
|
+
if (hit) { if (sessionTimer) clearTimeout(sessionTimer); sessionTimer = setTimeout(() => { applySessionText(); fixDialogs() }, 20) }
|
|
3800
3831
|
})
|
|
3801
3832
|
sessionObs.observe(document.body, { childList: true, subtree: true })
|
|
3802
3833
|
}
|
|
@@ -3817,9 +3848,11 @@ window.__ModuleLoader__.load({
|
|
|
3817
3848
|
}, 'dsh-long-plugins: glass applier')
|
|
3818
3849
|
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: 'RA-Span' }, GlassSettingsSection))
|
|
3819
3850
|
ctx.effect(() => {
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3851
|
+
// 玻璃配置/背景图历史拉取延迟到空闲, 减轻首屏网络风暴(背景没用玻璃时几乎零成本)
|
|
3852
|
+
const loadGlass = () => { fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
|
|
3853
|
+
if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ ...b.cfg, bgImage: b.bgImageUrl || '' }) }
|
|
3854
|
+
}).catch(() => {}) }
|
|
3855
|
+
try { if (window.requestIdleCallback) window.requestIdleCallback(loadGlass, { timeout: 500 }); else setTimeout(loadGlass, 500) } catch (_) { loadGlass() }
|
|
3823
3856
|
}, 'dsh-long-plugins: glass boot')
|
|
3824
3857
|
},
|
|
3825
3858
|
}
|
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.3.0",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -1632,15 +1632,48 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1632
1632
|
const glassDir = resolve(homedir(), ".dsh-long-plugins");
|
|
1633
1633
|
const glassFile = join(glassDir, "glass.json");
|
|
1634
1634
|
const bgDir = join(glassDir, "backgrounds");
|
|
1635
|
+
const currentBgFile = join(bgDir, "current.uri");
|
|
1636
|
+
// 背景图文件引用: 配置里 bgImage 为 "current"(存 bgDir/current.uri) 或 ""(无) ;不再内嵌大图 data-URI
|
|
1637
|
+
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; };
|
|
1638
|
+
const bgImageUrl = (cfg) => (cfg.bgImage === "current") ? "/api/dsh-uploads/glass-background" : "";
|
|
1639
|
+
// 只保留当前背景图(current.uri), 删除其它历史图片文件(旧图清理)
|
|
1640
|
+
async function pruneBackgrounds() {
|
|
1641
|
+
try {
|
|
1642
|
+
const names = await readdir(bgDir);
|
|
1643
|
+
for (const n of names) {
|
|
1644
|
+
if (n === "current.uri" || !n.endsWith(".uri")) continue;
|
|
1645
|
+
try { await unlink(join(bgDir, n)); } catch (_) {}
|
|
1646
|
+
}
|
|
1647
|
+
} catch (_) {}
|
|
1648
|
+
}
|
|
1635
1649
|
async function readGlassJSON() {
|
|
1636
|
-
try {
|
|
1637
|
-
|
|
1650
|
+
try {
|
|
1651
|
+
const raw = JSON.parse(await readFile(glassFile, "utf8"));
|
|
1652
|
+
const cfg = { ...GLASS_DEFAULTS, ...(raw && typeof raw === "object" ? raw : {}) };
|
|
1653
|
+
// 迁移旧版: bgImage 是内嵌 data-URI(大图) → 存成文件 + 改引用
|
|
1654
|
+
if (typeof cfg.bgImage === "string" && cfg.bgImage.startsWith("data:image/")) {
|
|
1655
|
+
try {
|
|
1656
|
+
await mkdir(bgDir, { recursive: true, mode: 0o700 });
|
|
1657
|
+
await writeFile(currentBgFile, cfg.bgImage, "utf8");
|
|
1658
|
+
cfg.bgImage = "current";
|
|
1659
|
+
await writeFile(glassFile, JSON.stringify(cfg, null, 2), "utf8");
|
|
1660
|
+
} catch (_) {}
|
|
1661
|
+
}
|
|
1662
|
+
return cfg;
|
|
1663
|
+
} catch { return { ...GLASS_DEFAULTS }; }
|
|
1638
1664
|
}
|
|
1639
1665
|
const glassConfig = async (req, res) => {
|
|
1640
1666
|
try {
|
|
1641
1667
|
requireTrusted(req);
|
|
1642
1668
|
if (req.method === "GET" || req.method === "HEAD") {
|
|
1643
1669
|
const cfg = await readGlassJSON();
|
|
1670
|
+
// 背景图只返回"是否存在 + 图片 URL", 不再返回内嵌大图 data-URI(减小响应)
|
|
1671
|
+
let imgMeta = { present: false, bytes: 0 };
|
|
1672
|
+
let imgUrl = "";
|
|
1673
|
+
if (cfg.bgImage === "current") {
|
|
1674
|
+
imgUrl = "/api/dsh-uploads/glass-background";
|
|
1675
|
+
try { imgMeta = { present: true, bytes: (await stat(currentBgFile)).size || 0 }; } catch (_) { imgMeta = { present: true, bytes: 0 }; }
|
|
1676
|
+
}
|
|
1644
1677
|
sendJson(res, 200, {
|
|
1645
1678
|
found: true,
|
|
1646
1679
|
cfg: {
|
|
@@ -1651,9 +1684,10 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1651
1684
|
bgTint: cfg.bgTint || { light: { color: "#1a2332", mask: 0.28 }, dark: { color: "#1a2332", mask: 0.28 } },
|
|
1652
1685
|
bgColor: cfg.bgColor || "#1a2332", bgMask: cfg.bgMask ?? 0.28,
|
|
1653
1686
|
bgBlur: cfg.bgBlur ?? 0, bgOpacity: cfg.bgOpacity ?? 1,
|
|
1654
|
-
bgImage:
|
|
1687
|
+
bgImage: imgMeta,
|
|
1655
1688
|
},
|
|
1656
|
-
bgImage:
|
|
1689
|
+
bgImage: imgMeta,
|
|
1690
|
+
bgImageUrl: imgUrl,
|
|
1657
1691
|
});
|
|
1658
1692
|
return;
|
|
1659
1693
|
}
|
|
@@ -1661,16 +1695,21 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1661
1695
|
const body = await readJsonBody(req, 4 * 1024 * 1024);
|
|
1662
1696
|
const current = await readGlassJSON();
|
|
1663
1697
|
const next = { ...current, ...sanitizeGlass(typeof body === "object" && body !== null ? body : {}) };
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
// 背景图若有变更且是 data-URI, 存入 backgrounds/ 供"选择旧图"使用
|
|
1667
|
-
if (next.bgImage && current.bgImage !== next.bgImage && /^data:image\//.test(next.bgImage)) {
|
|
1698
|
+
// 背景图改为"文件 + 引用": 用户上传的 data-URI 存入 current.uri, 配置只存 "current" 引用
|
|
1699
|
+
if (typeof next.bgImage === "string" && next.bgImage.startsWith("data:image/")) {
|
|
1668
1700
|
try {
|
|
1669
1701
|
await mkdir(bgDir, { recursive: true, mode: 0o700 });
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
} catch (_) {}
|
|
1702
|
+
await writeFile(currentBgFile, next.bgImage, "utf8");
|
|
1703
|
+
next.bgImage = "current";
|
|
1704
|
+
} catch (_) { next.bgImage = ""; }
|
|
1705
|
+
} else if ("bgImage" in body && body.bgImage === "") {
|
|
1706
|
+
next.bgImage = ""; // 显式清除
|
|
1707
|
+
try { await unlink(currentBgFile); } catch (_) {}
|
|
1673
1708
|
}
|
|
1709
|
+
// 只保留当前背景图, 删除历史旧图
|
|
1710
|
+
await pruneBackgrounds();
|
|
1711
|
+
await mkdir(glassDir, { recursive: true, mode: 0o700 });
|
|
1712
|
+
await writeFile(glassFile, JSON.stringify(next, null, 2), "utf8");
|
|
1674
1713
|
sendJson(res, 200, { ok: true, saved: true });
|
|
1675
1714
|
return;
|
|
1676
1715
|
}
|
|
@@ -1691,14 +1730,32 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1691
1730
|
catch { sendJson(res, 404, { error: "not found" }); return; }
|
|
1692
1731
|
}
|
|
1693
1732
|
let list = [];
|
|
1694
|
-
|
|
1733
|
+
// 只保留当前背景图, 删除历史旧图; 列表排除当前图
|
|
1734
|
+
try { await pruneBackgrounds(); } catch (_) {}
|
|
1735
|
+
try { list = (await readdir(bgDir)).filter((n) => n.endsWith(".uri") && n !== "current.uri").sort().reverse().slice(0, 30); } catch (_) {}
|
|
1695
1736
|
sendJson(res, 200, { list });
|
|
1696
1737
|
} catch (error) {
|
|
1697
1738
|
sendError(res, error, onError);
|
|
1698
1739
|
}
|
|
1699
1740
|
};
|
|
1741
|
+
// 返回当前背景图(文件存的 data-URI 解码为真实图片)
|
|
1742
|
+
const glassBackground = async (req, res) => {
|
|
1743
|
+
try {
|
|
1744
|
+
requireTrusted(req);
|
|
1745
|
+
const cfg = await readGlassJSON();
|
|
1746
|
+
if (cfg.bgImage !== "current") { sendJson(res, 404, { error: "no background" }); return; }
|
|
1747
|
+
const buf = await readFile(currentBgFile, "utf8");
|
|
1748
|
+
const d = decodeDataUri(buf);
|
|
1749
|
+
if (!d) { sendJson(res, 404, { error: "bad image" }); return; }
|
|
1750
|
+
const bytes = Buffer.from(d.base64, "base64");
|
|
1751
|
+
res.writeHead(200, { "content-type": d.mime, "cache-control": "no-store", "x-content-type-options": "nosniff" });
|
|
1752
|
+
res.end(bytes);
|
|
1753
|
+
} catch (error) {
|
|
1754
|
+
sendError(res, error, onError);
|
|
1755
|
+
}
|
|
1756
|
+
};
|
|
1700
1757
|
|
|
1701
|
-
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig, glassBackgrounds };
|
|
1758
|
+
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig, glassBackgrounds, glassBackground };
|
|
1702
1759
|
}
|
|
1703
1760
|
|
|
1704
1761
|
/** 人类可读文件大小。 */
|
|
@@ -2609,6 +2666,12 @@ export async function apply(ctx, config = {}) {
|
|
|
2609
2666
|
handler: handlers.glassBackgrounds,
|
|
2610
2667
|
}), "dsh-long-plugins: glass-ui backgrounds route");
|
|
2611
2668
|
|
|
2669
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2670
|
+
kind: "exact",
|
|
2671
|
+
path: "/api/dsh-uploads/glass-background",
|
|
2672
|
+
handler: handlers.glassBackground,
|
|
2673
|
+
}), "dsh-long-plugins: glass-ui background image route");
|
|
2674
|
+
|
|
2612
2675
|
ctx.effect(() => ctx.webServer.register({
|
|
2613
2676
|
kind: "exact",
|
|
2614
2677
|
path: "/api/dsh-uploads/workspace-browse",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"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 心跳、升级重连)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|