dsh-long-plugins 1.3.10 → 2.0.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/README.md +8 -0
- package/client/client.js +255 -0
- package/lib/index.js +78 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -124,6 +124,14 @@ MIT
|
|
|
124
124
|
|
|
125
125
|
## Changelog / 更新记录
|
|
126
126
|
|
|
127
|
+
### v2.0.0
|
|
128
|
+
- 新增:毛玻璃界面(玻璃拟态)。为 DSH 网页叠加自定义毛玻璃效果,将界面打磨成"玻璃"质感。
|
|
129
|
+
- **自定义背景图**:上传一张背景图铺满整个页面(含左边栏、顶部、会话区),实现桌面壁纸式的背景;左边栏 / 顶部半透明透出背景图,界面更通透。
|
|
130
|
+
- **分区独立磨砂与罩色**:会话区、输入区各自独立设置「罩色 + 罩强度」,互不影响,实现"玻璃卡片"的分区质感;会话窗口磨砂玻璃悬浮卡片效果。
|
|
131
|
+
- **磨砂模糊**:全局磨砂模糊强度可调(0–80px),文字清晰可读。
|
|
132
|
+
- **跟随系统主题**:界面文字/内容颜色跟随系统深色/浅色主题,不强制改色,保证任意主题下可读。
|
|
133
|
+
- **设置面板「毛玻璃界面」**:总开关、背景图上传、磨砂模糊、会话区/输入区各自的罩色与罩强度,实时保存生效。
|
|
134
|
+
|
|
127
135
|
### v1.3.10
|
|
128
136
|
- 新增 `.xlsx` 浏览器端预览:用 SheetJS 解析并显示为表格(行列标头、合并单元格、多工作表切换),预览支持 − / + / 适合宽度缩放,默认整表适配;浏览器端处理,不占用 NAS 本地资源。
|
|
129
137
|
- 工作区浏览、工作区文件、上传文件列表中的 `.xlsx` 均接入该预览页。
|
package/client/client.js
CHANGED
|
@@ -3034,6 +3034,259 @@ window.__ModuleLoader__.load({
|
|
|
3034
3034
|
},
|
|
3035
3035
|
}
|
|
3036
3036
|
|
|
3037
|
+
// ===== dsh-long-plugins: 毛玻璃界面 (glass UI) =====
|
|
3038
|
+
// 简洁方案:共用一张背景图铺满 body;左边栏 + 顶部各加一层半透明"罩";
|
|
3039
|
+
// 会话窗口(centerCol)做成磨砂玻璃悬浮卡片(backdrop-filter blur + 半透明背景)。
|
|
3040
|
+
const GLASS_CSS = `
|
|
3041
|
+
html[data-dsh-glass="on"] body { position: relative; }
|
|
3042
|
+
/* 方案A:背景图铺满整页(含左栏/顶栏/底部,全透明透出);仅会话滚动区做磨砂玻璃卡片。 */
|
|
3043
|
+
/* 左栏内部容器(或其它用此变量的)也透明,让背景图透到左栏。只覆盖左栏专用变量,避免影响设置面板。 */
|
|
3044
|
+
html[data-dsh-glass="on"] body {
|
|
3045
|
+
--dsw-specific-sidebar-fill: transparent !important;
|
|
3046
|
+
}
|
|
3047
|
+
/* 左栏 / 顶栏 / 底部:完全透明,让 frame 背景图连成一张透出(不做罩/不做磨砂) */
|
|
3048
|
+
html[data-dsh-glass="on"] #root [class*="sidebarCol"],
|
|
3049
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_header"],
|
|
3050
|
+
html[data-dsh-glass="on"] #root [class*="centerCol"],
|
|
3051
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_root"] {
|
|
3052
|
+
background-color: transparent !important;
|
|
3053
|
+
backdrop-filter: none;
|
|
3054
|
+
-webkit-backdrop-filter: none;
|
|
3055
|
+
}
|
|
3056
|
+
/* 左栏内部根 / 底部容器也用透明(这些是 DSH 当前版本哈希类名;背景图要透到左栏与底部) */
|
|
3057
|
+
html[data-dsh-glass="on"] #root [class*="hHd-Xa_root"],
|
|
3058
|
+
html[data-dsh-glass="on"] #root [class*="FJxK0a_root"] {
|
|
3059
|
+
background-color: transparent !important;
|
|
3060
|
+
}
|
|
3061
|
+
/* 会话滚动区:磨砂玻璃悬浮卡片(居中收窄,不占满宽度,四周透背景图) */
|
|
3062
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"] {
|
|
3063
|
+
background-color: var(--dsh-glass-session-bg, rgba(26,35,50,0.45)) !important;
|
|
3064
|
+
backdrop-filter: blur(var(--dsh-glass-blur, 16px));
|
|
3065
|
+
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px));
|
|
3066
|
+
border-radius: 16px;
|
|
3067
|
+
overflow: auto;
|
|
3068
|
+
margin: 0 auto !important;
|
|
3069
|
+
/* 柔和阴影 + 边缘渐隐,让磨砂卡片边缘过渡自然(悬浮玻璃质感) */
|
|
3070
|
+
box-shadow: 0 8px 40px rgba(0,0,0,0.28), 0 2px 12px rgba(0,0,0,0.18);
|
|
3071
|
+
}
|
|
3072
|
+
/* 滚动条:细、半透明、圆角,且左移贴在内容区(不占卡片最右边缘),让右边缘过渡柔和 */
|
|
3073
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar { width: 8px; }
|
|
3074
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-track { background: transparent; }
|
|
3075
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.35); border-radius: 8px; }
|
|
3076
|
+
html[data-dsh-glass="on"] #root [class*="wSkVaW_scrollBody"]::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.5); }
|
|
3077
|
+
/* 底部输入卡片:深色半透明磨砂(参考图:深色玻璃卡片 + 白字,与背景区分开) */
|
|
3078
|
+
html[data-dsh-glass="on"] #root [class*="uV2eYG_card"] {
|
|
3079
|
+
background-color: var(--dsh-glass-input-bg, rgba(26,35,50,0.6)) !important;
|
|
3080
|
+
backdrop-filter: blur(var(--dsh-glass-blur, 16px));
|
|
3081
|
+
-webkit-backdrop-filter: blur(var(--dsh-glass-blur, 16px));
|
|
3082
|
+
}
|
|
3083
|
+
|
|
3084
|
+
html[data-dsh-glass="off"] #root [class*="sidebarCol"],
|
|
3085
|
+
html[data-dsh-glass="off"] #root [class*="header"],
|
|
3086
|
+
html[data-dsh-glass="off"] #root [class*="centerCol"],
|
|
3087
|
+
html[data-dsh-glass="off"] #root [class*="wSkVaW_root"],
|
|
3088
|
+
html[data-dsh-glass="off"] #root [class*="wSkVaW_scrollBody"],
|
|
3089
|
+
html[data-dsh-glass="off"] #root [class*="uV2eYG_card"] { background-color: transparent !important; backdrop-filter: none; }
|
|
3090
|
+
.dsh-glass-section{display:flex;flex-direction:column;gap:16px;min-width:0;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
3091
|
+
.dsh-glass-head{display:flex;align-items:center;justify-content:space-between;gap:16px}
|
|
3092
|
+
.dsh-glass-head h2{margin:0;font-size:20px;line-height:28px}
|
|
3093
|
+
.dsh-glass-head p{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
3094
|
+
.dsh-glass-switch{display:inline-flex;align-items:center;gap:8px;font-size:13px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;user-select:none}
|
|
3095
|
+
.dsh-glass-switch input{width:34px;height:20px;accent-color:var(--dsw-alias-state-business-primary);cursor:pointer}
|
|
3096
|
+
.dsh-glass-card{display:grid;gap:12px;padding:14px 16px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-input-major)}
|
|
3097
|
+
.dsh-glass-zone-title{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
3098
|
+
.dsh-glass-row{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap}
|
|
3099
|
+
.dsh-glass-row label{font-size:13px;color:var(--dsw-alias-label-primary);flex:none}
|
|
3100
|
+
.dsh-glass-range{flex:1;min-width:160px;max-width:320px;accent-color:var(--dsw-alias-state-business-primary)}
|
|
3101
|
+
.dsh-glass-val{flex:none;font-size:12px;color:var(--dsw-alias-label-secondary);min-width:56px;text-align:right;font-variant-numeric:tabular-nums}
|
|
3102
|
+
.dsh-glass-swatches{display:flex;gap:8px;flex-wrap:wrap}
|
|
3103
|
+
.dsh-glass-swatch{width:26px;height:26px;border-radius:8px;border:1px solid var(--dsw-alias-border-l2);cursor:pointer;padding:0;transition:transform .12s}
|
|
3104
|
+
.dsh-glass-swatch:hover{transform:scale(1.12)}
|
|
3105
|
+
.dsh-glass-swatch.on{outline:2px solid var(--dsw-alias-state-business-primary);outline-offset:2px}
|
|
3106
|
+
.dsh-glass-colorpicker{display:inline-flex;align-items:center;gap:8px;padding:5px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;cursor:pointer}
|
|
3107
|
+
.dsh-glass-colorpicker input[type=color]{width:30px;height:24px;border:none;background:none;padding:0;cursor:pointer}
|
|
3108
|
+
.dsh-glass-bg{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
|
|
3109
|
+
.dsh-glass-file{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font-size:12px;cursor:pointer}
|
|
3110
|
+
.dsh-glass-file input{display:none}
|
|
3111
|
+
.dsh-glass-actions{display:flex;gap:8px;align-items:center;justify-content:flex-end}
|
|
3112
|
+
.dsh-glass-actions button{padding:7px 16px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);font:inherit;font-size:13px;cursor:pointer}
|
|
3113
|
+
.dsh-glass-actions .primary{background:var(--dsw-alias-state-business-primary);border-color:var(--dsw-alias-state-business-primary);color:#fff}
|
|
3114
|
+
.dsh-glass-actions button:disabled{opacity:.55;cursor:not-allowed}
|
|
3115
|
+
.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}
|
|
3116
|
+
.dsh-glass-note{margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
3117
|
+
`
|
|
3118
|
+
const GLASS_SWATCHES = ['#0f1420', '#14161a', '#1a2530', '#26324d', '#153a3a', '#3b2a4a', '#4a1f1f', '#2d3748']
|
|
3119
|
+
const glassText = (e) => (e && e.message) ? e.message : String(e)
|
|
3120
|
+
function glassImageInput(value, onChange) {
|
|
3121
|
+
return React.createElement('label', { className: 'dsh-glass-file' },
|
|
3122
|
+
React.createElement('span', null, '上传背景图'),
|
|
3123
|
+
React.createElement('input', { type: 'file', accept: 'image/png,image/jpeg,image/webp,image/gif', onChange: (e) => {
|
|
3124
|
+
const f = e.target.files && e.target.files[0]
|
|
3125
|
+
if (!f) return
|
|
3126
|
+
const reader = new FileReader()
|
|
3127
|
+
reader.onload = () => onChange(String(reader.result || ''))
|
|
3128
|
+
reader.readAsDataURL(f)
|
|
3129
|
+
} }),
|
|
3130
|
+
value ? React.createElement('span', { style: { width: 22, height: 22, borderRadius: 4, backgroundImage: 'url(' + value + ')', backgroundSize: 'cover', display: 'inline-block' } }) : null,
|
|
3131
|
+
)
|
|
3132
|
+
}
|
|
3133
|
+
// 会话区/输入区共用设置卡:罩色 + 罩强度
|
|
3134
|
+
const GlassZoneCard = ({ title, zone, onChange }) => {
|
|
3135
|
+
const z = zone || { color: '#1a2332', mask: 0.45 }
|
|
3136
|
+
const setPatch = (p) => onChange({ ...z, ...p })
|
|
3137
|
+
return React.createElement('div', { className: 'dsh-glass-card' },
|
|
3138
|
+
React.createElement('div', { className: 'dsh-glass-zone-title' }, title),
|
|
3139
|
+
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3140
|
+
React.createElement('label', null, title + ' 罩色'),
|
|
3141
|
+
React.createElement('div', { className: 'dsh-glass-swatches' },
|
|
3142
|
+
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 }) })),
|
|
3143
|
+
),
|
|
3144
|
+
React.createElement('label', { className: 'dsh-glass-colorpicker' }, '色盘', React.createElement('input', { type: 'color', value: z.color || '#1a2332', onChange: (e) => setPatch({ color: e.target.value }) })),
|
|
3145
|
+
),
|
|
3146
|
+
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3147
|
+
React.createElement('label', null, title + ' 罩强度'),
|
|
3148
|
+
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) }) }),
|
|
3149
|
+
React.createElement('span', { className: 'dsh-glass-val' }, `${Math.round((Number(z.mask ?? 0.45)) * 100)}%`),
|
|
3150
|
+
),
|
|
3151
|
+
)
|
|
3152
|
+
}
|
|
3153
|
+
const GlassSettingsSection = () => {
|
|
3154
|
+
const [state, setState] = React.useState({ loading: true, saving: false, error: '', cfg: null, bgImage: '' })
|
|
3155
|
+
const load = React.useCallback(async () => {
|
|
3156
|
+
setState((s) => ({ ...s, loading: true, error: '' }))
|
|
3157
|
+
try {
|
|
3158
|
+
const r = await fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' })
|
|
3159
|
+
const b = await r.json()
|
|
3160
|
+
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3161
|
+
setState((s) => ({ ...s, loading: false, cfg: b.cfg, bgImage: b.bgImage || '' }))
|
|
3162
|
+
} catch (e) { setState((s) => ({ ...s, loading: false, error: glassText(e) })) }
|
|
3163
|
+
}, [])
|
|
3164
|
+
React.useEffect(() => { load(); }, [load])
|
|
3165
|
+
const patch = React.useCallback((p) => setState((s) => ({ ...s, cfg: { ...(s.cfg || {}), ...p } })), [])
|
|
3166
|
+
const patchZone = React.useCallback((key, p) => setState((s) => ({ ...s, cfg: { ...(s.cfg || {}), [key]: p } })), [])
|
|
3167
|
+
const save = React.useCallback(async () => {
|
|
3168
|
+
setState((s) => ({ ...s, saving: true, error: '' }))
|
|
3169
|
+
try {
|
|
3170
|
+
const cfg = state.cfg || {}
|
|
3171
|
+
const payload = {
|
|
3172
|
+
enabled: !!cfg.enabled, blur: Number(cfg.blur) || 0,
|
|
3173
|
+
bgImage: state.bgImage || cfg.bgImage || '',
|
|
3174
|
+
zone: {
|
|
3175
|
+
session: { color: (cfg.session && cfg.session.color) || '#1a2332', mask: Number((cfg.session && cfg.session.mask)) || 0.45 },
|
|
3176
|
+
input: { color: (cfg.input && cfg.input.color) || '#1a2332', mask: Number((cfg.input && cfg.input.mask)) || 0.6 },
|
|
3177
|
+
},
|
|
3178
|
+
}
|
|
3179
|
+
const r = await fetch('/api/dsh-uploads/glass-config', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload) })
|
|
3180
|
+
const b = await r.json()
|
|
3181
|
+
if (!r.ok) throw new Error(b.error || ('HTTP ' + r.status))
|
|
3182
|
+
window.__dshGlassApply && window.__dshGlassApply(payload)
|
|
3183
|
+
setState((s) => ({ ...s, saving: false, cfg: { ...(s.cfg || {}), ...payload }, bgImage: payload.bgImage, error: '' }))
|
|
3184
|
+
} catch (e) { setState((s) => ({ ...s, saving: false, error: glassText(e) })) }
|
|
3185
|
+
}, [state])
|
|
3186
|
+
const cfg = state.cfg || {}
|
|
3187
|
+
if (state.loading) return React.createElement('div', { className: 'dsh-glass-section' }, '加载中…')
|
|
3188
|
+
return React.createElement('div', { className: 'dsh-glass-section' },
|
|
3189
|
+
React.createElement('div', { className: 'dsh-glass-head' },
|
|
3190
|
+
React.createElement('div', null, React.createElement('h2', null, '毛玻璃界面'), React.createElement('p', null, '共用一张背景图铺满整页,仅消息滚动区做居中磨砂玻璃卡片,其余区域透出背景图。')),
|
|
3191
|
+
React.createElement('label', { className: 'dsh-glass-switch' }, React.createElement('input', { type: 'checkbox', checked: !!cfg.enabled, onChange: (e) => patch({ enabled: e.target.checked }) }), cfg.enabled ? '已开启' : '已关闭'),
|
|
3192
|
+
),
|
|
3193
|
+
state.error ? React.createElement('div', { className: 'dsh-glass-error' }, state.error) : null,
|
|
3194
|
+
React.createElement('div', { className: 'dsh-glass-card' },
|
|
3195
|
+
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3196
|
+
React.createElement('label', null, '磨砂模糊强度'),
|
|
3197
|
+
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) }) }),
|
|
3198
|
+
React.createElement('span', { className: 'dsh-glass-val' }, `${Number(cfg.blur ?? 20)} px`),
|
|
3199
|
+
),
|
|
3200
|
+
React.createElement(GlassZoneCard, { title: '会话区', zone: cfg.session, onChange: (p) => patchZone('session', p) }),
|
|
3201
|
+
React.createElement(GlassZoneCard, { title: '输入区', zone: cfg.input, onChange: (p) => patchZone('input', p) }),
|
|
3202
|
+
React.createElement('div', { className: 'dsh-glass-row' },
|
|
3203
|
+
React.createElement('label', null, '共用背景图'),
|
|
3204
|
+
React.createElement('div', { className: 'dsh-glass-bg' }, glassImageInput(state.bgImage, (v) => setState((s) => ({ ...s, bgImage: v })))),
|
|
3205
|
+
),
|
|
3206
|
+
React.createElement('p', { className: 'dsh-glass-note' }, '提示:背景图铺满整页;仅消息滚动区做磨砂玻璃卡片;输入卡片与其余区域透出背景。'),
|
|
3207
|
+
React.createElement('div', { className: 'dsh-glass-actions' },
|
|
3208
|
+
React.createElement('button', { type: 'button', onClick: load }, '重置'),
|
|
3209
|
+
React.createElement('button', { type: 'button', className: 'primary', disabled: state.saving, onClick: save }, state.saving ? '保存中…' : '保存生效'),
|
|
3210
|
+
),
|
|
3211
|
+
),
|
|
3212
|
+
)
|
|
3213
|
+
}
|
|
3214
|
+
const glassPlugin = {
|
|
3215
|
+
inject: ['slots'],
|
|
3216
|
+
apply(ctx) {
|
|
3217
|
+
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' }
|
|
3218
|
+
ctx.effect(() => {
|
|
3219
|
+
const style = document.createElement('style')
|
|
3220
|
+
style.dataset.plugin = 'dsh-long-plugins'
|
|
3221
|
+
style.dataset.pluginCss = 'dsh-long-plugins/glass'
|
|
3222
|
+
style.textContent = GLASS_CSS
|
|
3223
|
+
document.head.appendChild(style)
|
|
3224
|
+
return () => style.remove()
|
|
3225
|
+
}, 'dsh-long-plugins: glass styles')
|
|
3226
|
+
ctx.effect(() => {
|
|
3227
|
+
const root = document.documentElement
|
|
3228
|
+
|
|
3229
|
+
// 磨砂卡片与底部输入卡片对齐:宽度取输入卡片宽度,translateX 补偿使左右边界对齐输入卡。
|
|
3230
|
+
const centerScroll = () => {
|
|
3231
|
+
const el = document.querySelector('#root [class*="wSkVaW_scrollBody"]')
|
|
3232
|
+
if (!el) return
|
|
3233
|
+
const card = document.querySelector('#root [class*="uV2eYG_card"]')
|
|
3234
|
+
const r = el.getBoundingClientRect()
|
|
3235
|
+
const target = card ? card.getBoundingClientRect() : null
|
|
3236
|
+
// 不改磨砂卡片宽度(保留默认 width:min() 原始宽度),仅水平居中到输入卡片中心。
|
|
3237
|
+
const diff = (target ? (target.left + target.right) / 2 : window.innerWidth / 2) - (r.left + r.right) / 2
|
|
3238
|
+
el.style.transform = 'translateX(' + Math.round(diff) + 'px)'
|
|
3239
|
+
}
|
|
3240
|
+
window.addEventListener('resize', centerScroll)
|
|
3241
|
+
window.__dshGlassCenter = centerScroll
|
|
3242
|
+
// win 风格文字:给左栏/顶栏内的可见文字元素加白色+深色描边;跳过弹窗(modal/overlay/dialog/settings/panel)内元素。
|
|
3243
|
+
|
|
3244
|
+
window.__dshGlassApply = (cfg) => {
|
|
3245
|
+
const c = cfg || {}
|
|
3246
|
+
root.setAttribute('data-dsh-glass', c.enabled ? 'on' : 'off')
|
|
3247
|
+
// ... 后续 applier 逻辑在下方
|
|
3248
|
+
const blur = Number(c.blur) || 16
|
|
3249
|
+
root.style.setProperty('--dsh-glass-blur', blur + 'px')
|
|
3250
|
+
const zs = (c.zone && c.zone.session) || { color: '#1a2332', mask: 0.45 }
|
|
3251
|
+
const zi = (c.zone && c.zone.input) || { color: '#1a2332', mask: 0.6 }
|
|
3252
|
+
root.style.setProperty('--dsh-glass-session-bg', `rgba(${hexToRgb(zs.color)},${Math.min(0.95, Math.max(0, Number(zs.mask) || 0.45))})`)
|
|
3253
|
+
root.style.setProperty('--dsh-glass-input-bg', `rgba(${hexToRgb(zi.color)},${Math.min(0.95, Math.max(0, Number(zi.mask) || 0.6))})`)
|
|
3254
|
+
|
|
3255
|
+
const body = document.body
|
|
3256
|
+
const frame = document.querySelector('#root > div > div') || document.querySelector('#root > div')
|
|
3257
|
+
const applyBg = (el) => {
|
|
3258
|
+
if (!el) return
|
|
3259
|
+
if (c.enabled && c.bgImage) {
|
|
3260
|
+
el.style.backgroundImage = `url('${c.bgImage}')`
|
|
3261
|
+
el.style.backgroundSize = 'cover'
|
|
3262
|
+
el.style.backgroundPosition = 'center'
|
|
3263
|
+
el.style.backgroundRepeat = 'no-repeat'
|
|
3264
|
+
} else {
|
|
3265
|
+
el.style.backgroundImage = 'none'
|
|
3266
|
+
el.style.backgroundSize = ''
|
|
3267
|
+
el.style.backgroundPosition = ''
|
|
3268
|
+
el.style.backgroundRepeat = ''
|
|
3269
|
+
}
|
|
3270
|
+
}
|
|
3271
|
+
applyBg(body)
|
|
3272
|
+
if (frame) { frame.style.backgroundColor = 'transparent'; applyBg(frame) }
|
|
3273
|
+
centerScroll()
|
|
3274
|
+
}
|
|
3275
|
+
return () => {
|
|
3276
|
+
window.__dshGlassApply = undefined
|
|
3277
|
+
window.removeEventListener('resize', centerScroll)
|
|
3278
|
+
window.__dshGlassCenter = undefined
|
|
3279
|
+
}
|
|
3280
|
+
}, 'dsh-long-plugins: glass applier')
|
|
3281
|
+
ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'glass-ui', order: 40, label: '毛玻璃界面' }, GlassSettingsSection))
|
|
3282
|
+
ctx.effect(() => {
|
|
3283
|
+
fetch('/api/dsh-uploads/glass-config', { cache: 'no-store' }).then((r) => r.json()).then((b) => {
|
|
3284
|
+
if (b && b.cfg) { window.__dshGlassApply && window.__dshGlassApply({ enabled: b.cfg.enabled, blur: b.cfg.blur, mask: b.cfg.mask, color: b.cfg.color, bgImage: b.bgImage }) }
|
|
3285
|
+
}).catch(() => {})
|
|
3286
|
+
}, 'dsh-long-plugins: glass boot')
|
|
3287
|
+
},
|
|
3288
|
+
}
|
|
3289
|
+
|
|
3037
3290
|
const inject = Array.from(new Set([
|
|
3038
3291
|
...uploadPlugin.inject,
|
|
3039
3292
|
...skillDocsPlugin.inject,
|
|
@@ -3041,6 +3294,7 @@ window.__ModuleLoader__.load({
|
|
|
3041
3294
|
...mobilePlugin.inject,
|
|
3042
3295
|
...workspaceFilesPlugin.inject,
|
|
3043
3296
|
...turnRulerPlugin.inject,
|
|
3297
|
+
...glassPlugin.inject,
|
|
3044
3298
|
]))
|
|
3045
3299
|
|
|
3046
3300
|
function apply(ctx) {
|
|
@@ -3057,6 +3311,7 @@ window.__ModuleLoader__.load({
|
|
|
3057
3311
|
safeApply('mobile-hamburger', (c) => mobilePlugin.apply(c))
|
|
3058
3312
|
safeApply('workspace-files', (c) => workspaceFilesPlugin.apply(c))
|
|
3059
3313
|
safeApply('turn-ruler', (c) => turnRulerPlugin.apply(c))
|
|
3314
|
+
safeApply('glass', (c) => glassPlugin.apply(c))
|
|
3060
3315
|
}
|
|
3061
3316
|
|
|
3062
3317
|
exports.apply = apply
|
package/lib/index.js
CHANGED
|
@@ -666,6 +666,32 @@ async function officePreviewHtml(name, buffer) {
|
|
|
666
666
|
return null;
|
|
667
667
|
}
|
|
668
668
|
|
|
669
|
+
/** 仅保留毛玻璃配置允许字段并 clamp,避免写入任意/超大内容。背景图只收 data:image/*;base64 且限 2MB。 */
|
|
670
|
+
function sanitizeGlass(input) {
|
|
671
|
+
const out = {};
|
|
672
|
+
if ("enabled" in input) out.enabled = input.enabled === true;
|
|
673
|
+
if ("blur" in input) { const n = Number(input.blur); out.blur = Number.isFinite(n) ? Math.max(0, Math.min(80, Math.round(n))) : undefined; }
|
|
674
|
+
if ("mask" in input) { const n = Number(input.mask); out.mask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : undefined; }
|
|
675
|
+
if ("color" in input) out.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(input.color)) ? String(input.color) : undefined;
|
|
676
|
+
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; };
|
|
677
|
+
if ("bgImage" in input) out.bgImage = sanitizeUri(input.bgImage);
|
|
678
|
+
// 会话区/输入区各自独立罩色+罩强度
|
|
679
|
+
if (input.zone && typeof input.zone === "object") {
|
|
680
|
+
const zo = {};
|
|
681
|
+
for (const key of ["session", "input"]) {
|
|
682
|
+
const z = input.zone[key];
|
|
683
|
+
if (z && typeof z === "object") {
|
|
684
|
+
const o = {};
|
|
685
|
+
if ("color" in z) o.color = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(String(z.color)) ? String(z.color) : undefined;
|
|
686
|
+
if ("mask" in z) { const n = Number(z.mask); o.mask = Number.isFinite(n) ? Math.max(0, Math.min(0.95, n)) : undefined; }
|
|
687
|
+
zo[key] = Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
if (Object.keys(zo).length) out.zone = zo;
|
|
691
|
+
}
|
|
692
|
+
return Object.fromEntries(Object.entries(out).filter(([, v]) => v !== undefined));
|
|
693
|
+
}
|
|
694
|
+
|
|
669
695
|
export function createHandlers(options = {}) {
|
|
670
696
|
const root = resolve(options.root || resolveUploadRoot());
|
|
671
697
|
const maxFileBytes = positiveInteger(options.maxFileBytes, resolveMaxFileBytes());
|
|
@@ -1554,7 +1580,52 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1554
1580
|
}
|
|
1555
1581
|
};
|
|
1556
1582
|
|
|
1557
|
-
|
|
1583
|
+
// ---- 毛玻璃界面 (glass UI) 配置:读写 ~/.dsh-long-plugins/glass.json ----
|
|
1584
|
+
// 背景图以 data-URI 存进配置,随配置持久化。enabled=总开关;blur=磨砂模糊;mask=整页罩强度;
|
|
1585
|
+
// color=叠加/罩色;bgImage=共用背景图。顶栏/左栏的"罩"与整页共用 mask+color(不再单独存,简化)。
|
|
1586
|
+
const GLASS_DEFAULTS = {
|
|
1587
|
+
enabled: false, blur: 20, bgImage: "",
|
|
1588
|
+
zone: { session: { color: "#1a2332", mask: 0.45 }, input: { color: "#1a2332", mask: 0.6 } },
|
|
1589
|
+
};
|
|
1590
|
+
const glassDir = resolve(homedir(), ".dsh-long-plugins");
|
|
1591
|
+
const glassFile = join(glassDir, "glass.json");
|
|
1592
|
+
async function readGlassJSON() {
|
|
1593
|
+
try { const raw = JSON.parse(await readFile(glassFile, "utf8")); return { ...GLASS_DEFAULTS, ...(raw && typeof raw === "object" ? raw : {}) }; }
|
|
1594
|
+
catch { return { ...GLASS_DEFAULTS }; }
|
|
1595
|
+
}
|
|
1596
|
+
const glassConfig = async (req, res) => {
|
|
1597
|
+
try {
|
|
1598
|
+
requireTrusted(req);
|
|
1599
|
+
if (req.method === "GET" || req.method === "HEAD") {
|
|
1600
|
+
const cfg = await readGlassJSON();
|
|
1601
|
+
sendJson(res, 200, {
|
|
1602
|
+
found: true,
|
|
1603
|
+
cfg: {
|
|
1604
|
+
enabled: cfg.enabled, blur: cfg.blur,
|
|
1605
|
+
session: cfg.zone?.session || { color: "#1a2332", mask: 0.45 },
|
|
1606
|
+
input: cfg.zone?.input || { color: "#1a2332", mask: 0.6 },
|
|
1607
|
+
bgImage: cfg.bgImage ? { present: true, bytes: Math.round((cfg.bgImage.length * 3) / 4) } : { present: false },
|
|
1608
|
+
},
|
|
1609
|
+
bgImage: cfg.bgImage,
|
|
1610
|
+
});
|
|
1611
|
+
return;
|
|
1612
|
+
}
|
|
1613
|
+
if (req.method === "POST") {
|
|
1614
|
+
const body = await readJsonBody(req);
|
|
1615
|
+
const current = await readGlassJSON();
|
|
1616
|
+
const next = { ...current, ...sanitizeGlass(typeof body === "object" && body !== null ? body : {}) };
|
|
1617
|
+
await mkdir(glassDir, { recursive: true, mode: 0o700 });
|
|
1618
|
+
await writeFile(glassFile, JSON.stringify(next, null, 2), "utf8");
|
|
1619
|
+
sendJson(res, 200, { ok: true, saved: true });
|
|
1620
|
+
return;
|
|
1621
|
+
}
|
|
1622
|
+
methodNotAllowed(res, ["GET", "HEAD", "POST"]);
|
|
1623
|
+
} catch (error) {
|
|
1624
|
+
sendError(res, error, onError);
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage, xlsxPreviewPage, xlsxPreviewAsset, glassConfig };
|
|
1558
1629
|
}
|
|
1559
1630
|
|
|
1560
1631
|
/** 人类可读文件大小。 */
|
|
@@ -2453,6 +2524,12 @@ export async function apply(ctx, config = {}) {
|
|
|
2453
2524
|
handler: handlers.xlsxPreviewAsset,
|
|
2454
2525
|
}), "dsh-long-plugins: xlsx-preview vendor assets");
|
|
2455
2526
|
|
|
2527
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2528
|
+
kind: "exact",
|
|
2529
|
+
path: "/api/dsh-uploads/glass-config",
|
|
2530
|
+
handler: handlers.glassConfig,
|
|
2531
|
+
}), "dsh-long-plugins: glass-ui config route");
|
|
2532
|
+
|
|
2456
2533
|
ctx.effect(() => ctx.webServer.register({
|
|
2457
2534
|
kind: "exact",
|
|
2458
2535
|
path: "/api/dsh-uploads/workspace-browse",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.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",
|