dsh-long-plugins 1.3.9 → 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 +22 -1
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/client/client.js +284 -9
- package/client/vendor/xlsx.full.min.js +22 -0
- package/lib/index.js +408 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ upload manager, workspace **输出文件** section, top-bar **文件** browser w
|
|
|
5
5
|
**技能文档** (skill docs) browser, DeepSeek account balance, and **turn ruler (session navigation)** — all in one installable plugin.
|
|
6
6
|
Also ships an auto-repair install that relinks DSH core patches (reverse-proxy WebSocket heartbeat, upgrade relink).
|
|
7
7
|
|
|
8
|
-
一个插件整合 DSH Web 的常用增强:上传管理、顶栏「📂文件」工作区浏览 + 文件预览(Word、PPT 格式渲染)、工作区「输出文件」面板、技能文档浏览、账户余额显示。并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。会话记录导航。
|
|
8
|
+
一个插件整合 DSH Web 的常用增强:上传管理、顶栏「📂文件」工作区浏览 + 文件预览(Word、PPT、Excel 格式渲染)、工作区「输出文件」面板、技能文档浏览、账户余额显示。并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。会话记录导航。
|
|
9
9
|
|
|
10
10
|
## Features / 功能
|
|
11
11
|
|
|
@@ -95,6 +95,8 @@ Agent 可直接调用 `md2docx` 工具,把 Markdown 转成带页码页脚的 W
|
|
|
95
95
|
/api/dsh-uploads/workspace-file/save save edited content (POST)
|
|
96
96
|
/api/dsh-uploads/docx-preview Word (.docx) real render page (browser-side docx-preview)
|
|
97
97
|
/api/dsh-uploads/docx-preview-asset serve docx-preview / jszip vendor libs
|
|
98
|
+
/api/dsh-uploads/xlsx-preview Excel (.xlsx) static render page (browser-side SheetJS → table)
|
|
99
|
+
/api/dsh-uploads/xlsx-preview-asset serve SheetJS (xlsx) vendor lib
|
|
98
100
|
/dsh-skill-docs/skill-docs skill docs list (grouped by skill)
|
|
99
101
|
/dsh-skill-docs/skill-doc preview / download
|
|
100
102
|
/dsh-skill-docs/skill-doc/save save (POST)
|
|
@@ -115,7 +117,26 @@ MIT
|
|
|
115
117
|
- `JSZip` — 解压 Office 包 — **MIT**(或 GPLv3,本项目按 MIT 使用)
|
|
116
118
|
- `Chart.js` — PPT 内图表 — **MIT**
|
|
117
119
|
- `PptxViewJS` — PowerPoint 真实预览 — **MIT**
|
|
120
|
+
- `SheetJS`(xlsx.full.min.js) — Excel 真实预览 — **Apache-2.0**
|
|
118
121
|
- npm 依赖:`mammoth`(BSD-2-Clause)、`exceljs`(MIT)
|
|
119
122
|
|
|
120
123
|
完整的版权声明与来源见 [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)。
|
|
121
124
|
|
|
125
|
+
## Changelog / 更新记录
|
|
126
|
+
|
|
127
|
+
### v2.0.0
|
|
128
|
+
- 新增:毛玻璃界面(玻璃拟态)。为 DSH 网页叠加自定义毛玻璃效果,将界面打磨成"玻璃"质感。
|
|
129
|
+
- **自定义背景图**:上传一张背景图铺满整个页面(含左边栏、顶部、会话区),实现桌面壁纸式的背景;左边栏 / 顶部半透明透出背景图,界面更通透。
|
|
130
|
+
- **分区独立磨砂与罩色**:会话区、输入区各自独立设置「罩色 + 罩强度」,互不影响,实现"玻璃卡片"的分区质感;会话窗口磨砂玻璃悬浮卡片效果。
|
|
131
|
+
- **磨砂模糊**:全局磨砂模糊强度可调(0–80px),文字清晰可读。
|
|
132
|
+
- **跟随系统主题**:界面文字/内容颜色跟随系统深色/浅色主题,不强制改色,保证任意主题下可读。
|
|
133
|
+
- **设置面板「毛玻璃界面」**:总开关、背景图上传、磨砂模糊、会话区/输入区各自的罩色与罩强度,实时保存生效。
|
|
134
|
+
|
|
135
|
+
### v1.3.10
|
|
136
|
+
- 新增 `.xlsx` 浏览器端预览:用 SheetJS 解析并显示为表格(行列标头、合并单元格、多工作表切换),预览支持 − / + / 适合宽度缩放,默认整表适配;浏览器端处理,不占用 NAS 本地资源。
|
|
137
|
+
- 工作区浏览、工作区文件、上传文件列表中的 `.xlsx` 均接入该预览页。
|
|
138
|
+
- `.csv` / `.tsv` 保留表格预览(首行作表头)。
|
|
139
|
+
- 文件列表按钮顺序调整:工作区浏览页为 复制路径、重命名、下载、预览;上传/输出文件面板为 复制路径、删除、下载、预览。
|
|
140
|
+
- 「上传文件」「输出文件」新增「开启删除」开关:关闭时删除按钮置灰禁用,开启后可删除;上传文件的「复制路径」不再使用删除红色。
|
|
141
|
+
- 手机端(≤640px)文件列表顶部操作行保持单行:搜索/日期等宽、刷新/开关靠右。
|
|
142
|
+
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -34,6 +34,13 @@ copyright notices and sources are listed here.
|
|
|
34
34
|
- npm: <https://www.npmjs.com/package/pptxviewjs>
|
|
35
35
|
- CDN / source build: <https://cdn.jsdelivr.net/npm/pptxviewjs@1.1.9>
|
|
36
36
|
|
|
37
|
+
### SheetJS (`xlsx.full.min.js`)
|
|
38
|
+
- Used for: real browser-side Excel (`.xlsx`) preview
|
|
39
|
+
- License: **Apache-2.0**
|
|
40
|
+
- Copyright: SheetJS Community Edition
|
|
41
|
+
- npm: <https://www.npmjs.com/package/xlsx>
|
|
42
|
+
- CDN / source build: <https://cdn.jsdelivr.net/npm/xlsx@0.18.5>
|
|
43
|
+
|
|
37
44
|
## Runtime npm dependencies (not vendored into the package)
|
|
38
45
|
|
|
39
46
|
These are declared as `dependencies` and resolved on the host at install time;
|
package/client/client.js
CHANGED
|
@@ -718,6 +718,7 @@ window.__ModuleLoader__.load({
|
|
|
718
718
|
const [previewMaximized, setPreviewMaximized] = React.useState(false)
|
|
719
719
|
const [dayFilter, setDayFilter] = React.useState('')
|
|
720
720
|
const [search, setSearch] = React.useState('')
|
|
721
|
+
const [deleteEnabled, setDeleteEnabled] = React.useState(false)
|
|
721
722
|
|
|
722
723
|
async function refresh() {
|
|
723
724
|
setState((current) => ({ ...current, loading: true, error: '' }))
|
|
@@ -852,6 +853,12 @@ window.__ModuleLoader__.load({
|
|
|
852
853
|
{ type: 'button', className: 'dsh-upload-refresh', disabled: state.loading, onClick: refresh },
|
|
853
854
|
state.loading ? '刷新中…' : '刷新',
|
|
854
855
|
),
|
|
856
|
+
React.createElement(
|
|
857
|
+
'label',
|
|
858
|
+
{ className: 'dsh-upload-del-toggle' },
|
|
859
|
+
React.createElement('input', { type: 'checkbox', checked: deleteEnabled, onChange: (e) => setDeleteEnabled(e.target.checked) }),
|
|
860
|
+
'开启删除',
|
|
861
|
+
),
|
|
855
862
|
),
|
|
856
863
|
),
|
|
857
864
|
React.createElement(
|
|
@@ -941,11 +948,9 @@ window.__ModuleLoader__.load({
|
|
|
941
948
|
React.createElement(
|
|
942
949
|
'div',
|
|
943
950
|
{ className: 'dsh-upload-actions' },
|
|
944
|
-
React.createElement('button', { type: 'button', className: 'dsh-upload-preview', onClick: () => previewFile(file.name) }, '预览'),
|
|
945
|
-
React.createElement('a', { href: downloadUrl(file.name), download: file.name }, '下载'),
|
|
946
951
|
React.createElement(
|
|
947
952
|
'button',
|
|
948
|
-
{ type: 'button', onClick: () => {
|
|
953
|
+
{ type: 'button', className: 'dsh-upload-copy', onClick: () => {
|
|
949
954
|
const s = String(file.path || file.name)
|
|
950
955
|
const base = state.root ? String(state.root).replace(/\/[^/]*$/, '') : ''
|
|
951
956
|
copyText(base && s.indexOf(base) === 0 ? s.slice(base.length).replace(/^\/+/, '') : s)
|
|
@@ -954,9 +959,11 @@ window.__ModuleLoader__.load({
|
|
|
954
959
|
),
|
|
955
960
|
React.createElement(
|
|
956
961
|
'button',
|
|
957
|
-
{ type: 'button', disabled: deleting === file.name, onClick: () => remove(file.name) },
|
|
962
|
+
{ type: 'button', disabled: !deleteEnabled || deleting === file.name, onClick: () => remove(file.name) },
|
|
958
963
|
deleting === file.name ? '删除中…' : '删除',
|
|
959
964
|
),
|
|
965
|
+
React.createElement('a', { href: downloadUrl(file.name), download: file.name }, '下载'),
|
|
966
|
+
React.createElement('button', { type: 'button', className: 'dsh-upload-preview', onClick: () => previewFile(file.name) }, '预览'),
|
|
960
967
|
),
|
|
961
968
|
)),
|
|
962
969
|
)),
|
|
@@ -1024,8 +1031,13 @@ window.__ModuleLoader__.load({
|
|
|
1024
1031
|
.dsh-upload-file-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
1025
1032
|
.dsh-upload-actions{display:flex;flex:none;gap:7px}
|
|
1026
1033
|
.dsh-upload-actions button{color:var(--dsw-alias-state-error-primary)}
|
|
1034
|
+
.dsh-upload-actions button.dsh-upload-copy{color:var(--dsw-alias-label-primary)}
|
|
1035
|
+
.dsh-upload-actions button:disabled{opacity:.5;cursor:not-allowed;color:var(--dsw-alias-label-tertiary)}
|
|
1036
|
+
.dsh-upload-del-toggle{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--dsw-alias-label-secondary);cursor:pointer;white-space:nowrap;user-select:none}
|
|
1037
|
+
.dsh-upload-del-toggle input{accent-color:var(--dsw-alias-state-business-primary);cursor:pointer}
|
|
1038
|
+
.dsh-ws-del:disabled{opacity:.5;cursor:not-allowed;color:var(--dsw-alias-label-tertiary)!important}
|
|
1027
1039
|
.dsh-upload-actions a,.dsh-upload-actions button{white-space:nowrap}
|
|
1028
|
-
@media (max-width:640px){.dsh-upload-row{align-items:stretch;flex-direction:column;gap:4px}.dsh-upload-file-name{white-space:normal;word-break:break-all}.dsh-upload-actions{width:100%;display:flex;gap:6px}.dsh-upload-actions a,.dsh-upload-actions button{flex:1;text-align:center;white-space:nowrap;padding:6px 4px}.dsh-upload-chip{min-width:160px}.dsh-upload-settings-head{flex-direction:column;align-items:stretch;gap:10px}.dsh-upload-head-actions{width:100%;justify-content:flex-
|
|
1040
|
+
@media (max-width:640px){.dsh-upload-row{align-items:stretch;flex-direction:column;gap:4px}.dsh-upload-file-name{white-space:normal;word-break:break-all}.dsh-upload-actions{width:100%;display:flex;gap:6px}.dsh-upload-actions a,.dsh-upload-actions button{flex:1;text-align:center;white-space:nowrap;padding:6px 4px}.dsh-upload-chip{min-width:160px}.dsh-upload-settings-head{flex-direction:column;align-items:stretch;gap:10px}.dsh-upload-head-actions{width:100%;flex-wrap:nowrap;justify-content:flex-start}.dsh-upload-head-actions .dsh-searchpop,.dsh-upload-head-actions .dsh-datefilter{flex:1 1 0;min-width:0}.dsh-upload-head-actions .dsh-upload-refresh{flex:none;text-align:center;padding:7px 8px;max-width:none}.dsh-upload-head-actions .dsh-upload-del-toggle{flex:none;white-space:nowrap;padding:0 4px}}
|
|
1029
1041
|
.dsh-ws-folder:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1030
1042
|
.dsh-ws-file-type{flex:none;font-size:10px;line-height:14px;padding:1px 6px;border-radius:5px;border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-interactive-bg-hover);white-space:nowrap}
|
|
1031
1043
|
.dsh-upload-preview-del{color:var(--dsw-alias-state-error-primary)!important}
|
|
@@ -1068,6 +1080,7 @@ window.__ModuleLoader__.load({
|
|
|
1068
1080
|
const [collapsed, setCollapsed] = React.useState({})
|
|
1069
1081
|
const [dayFilter, setDayFilter] = React.useState('')
|
|
1070
1082
|
const [search, setSearch] = React.useState('')
|
|
1083
|
+
const [deleteEnabled, setDeleteEnabled] = React.useState(false)
|
|
1071
1084
|
const [copied, setCopied] = React.useState(false)
|
|
1072
1085
|
const [editing, setEditing] = React.useState(false)
|
|
1073
1086
|
const [edited, setEdited] = React.useState('')
|
|
@@ -1129,6 +1142,9 @@ window.__ModuleLoader__.load({
|
|
|
1129
1142
|
} else if (/\.pptx$/i.test(path)) {
|
|
1130
1143
|
// pptx:走 PptxViewJS 真实渲染页(浏览器端 Canvas 渲染,所见即所得)。
|
|
1131
1144
|
setPreview({ path, name: data.name, url: '/api/dsh-uploads/pptx-preview?path=' + encodeURIComponent(path) })
|
|
1145
|
+
} else if (/\.xlsx$/i.test(path)) {
|
|
1146
|
+
// xlsx:走 x-spreadsheet 真实渲染页(浏览器端解析 xlsx,所见即所得)。
|
|
1147
|
+
setPreview({ path, name: data.name, url: '/api/dsh-uploads/xlsx-preview?path=' + encodeURIComponent(path) })
|
|
1132
1148
|
} else {
|
|
1133
1149
|
setPreview(data)
|
|
1134
1150
|
}
|
|
@@ -1267,6 +1283,10 @@ window.__ModuleLoader__.load({
|
|
|
1267
1283
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', minWidth: 0 } },
|
|
1268
1284
|
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
1269
1285
|
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
1286
|
+
React.createElement('label', { className: 'dsh-upload-del-toggle' },
|
|
1287
|
+
React.createElement('input', { type: 'checkbox', checked: deleteEnabled, onChange: (e) => setDeleteEnabled(e.target.checked) }),
|
|
1288
|
+
'开启删除',
|
|
1289
|
+
),
|
|
1270
1290
|
),
|
|
1271
1291
|
error !== null && React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, error),
|
|
1272
1292
|
groups === null && error === null && React.createElement('div', { style: metaStyle }, '加载中…'),
|
|
@@ -1299,10 +1319,10 @@ window.__ModuleLoader__.load({
|
|
|
1299
1319
|
React.createElement(
|
|
1300
1320
|
'div',
|
|
1301
1321
|
{ className: 'dsh-ws-actions', style: { display: 'flex', gap: 6, flex: 'none' } },
|
|
1302
|
-
React.createElement('button', { type: 'button', style: btnStyle, disabled: busy, onClick: () => openPreview(f.path) }, '预览'),
|
|
1303
|
-
React.createElement('a', { href: '/api/dsh-uploads/workspace-file?path=' + encodeURIComponent(f.path) + '&download=1', download: f.name, style: btnStyle }, '下载'),
|
|
1304
1322
|
React.createElement('button', { type: 'button', style: btnStyle, disabled: busy, onClick: () => copyText(f.path) }, '复制路径'),
|
|
1305
|
-
React.createElement('button', { type: 'button', style: delStyle, disabled: busy, onClick: () => doDelete(f.path) }, '删除'),
|
|
1323
|
+
React.createElement('button', { type: 'button', className: 'dsh-ws-del', style: delStyle, disabled: !deleteEnabled || busy, onClick: () => doDelete(f.path) }, '删除'),
|
|
1324
|
+
React.createElement('a', { href: '/api/dsh-uploads/workspace-file?path=' + encodeURIComponent(f.path) + '&download=1', download: f.name, style: btnStyle }, '下载'),
|
|
1325
|
+
React.createElement('button', { type: 'button', style: btnStyle, disabled: busy, onClick: () => openPreview(f.path) }, '预览'),
|
|
1306
1326
|
),
|
|
1307
1327
|
)),
|
|
1308
1328
|
)),
|
|
@@ -1320,7 +1340,7 @@ window.__ModuleLoader__.load({
|
|
|
1320
1340
|
editing && React.createElement('button', { type: 'button', style: btnStyle, disabled: busy, onClick: doSave }, savedFlash ? '已保存' : '保存'),
|
|
1321
1341
|
React.createElement('button', { type: 'button', style: btnStyle, onClick: copyContent }, copied ? '已复制' : '复制全部'),
|
|
1322
1342
|
(preview.url !== void 0 || preview.officeHtml !== void 0 || preview.mdHtml !== void 0) && preview.loading !== true && React.createElement('a', { href: '/api/dsh-uploads/workspace-file?path=' + encodeURIComponent(preview.path) + '&download=1', download: preview.name, style: { ...btnStyle, color: 'var(--dsw-alias-state-business-primary)' } }, '下载'),
|
|
1323
|
-
(preview.url !== void 0 || preview.officeHtml !== void 0 || preview.mdHtml !== void 0) && preview.loading !== true && React.createElement('a', { href: '/api/dsh-uploads/workspace-preview?path=' + encodeURIComponent(preview.path), target: '_blank', rel: 'noopener noreferrer', style: { ...btnStyle, color: 'var(--dsw-alias-state-business-primary)' } }, '打开'),
|
|
1343
|
+
(preview.url !== void 0 || preview.officeHtml !== void 0 || preview.mdHtml !== void 0) && preview.loading !== true && React.createElement('a', { href: preview.url !== void 0 ? preview.url : '/api/dsh-uploads/workspace-preview?path=' + encodeURIComponent(preview.path), target: '_blank', rel: 'noopener noreferrer', style: { ...btnStyle, color: 'var(--dsw-alias-state-business-primary)' } }, '打开'),
|
|
1324
1344
|
React.createElement('button', { type: 'button', style: delStyle, disabled: busy, onClick: () => doDelete(preview.path) }, '删除'),
|
|
1325
1345
|
React.createElement('button', { type: 'button', style: btnStyle, onClick: () => setMaximized((m) => !m) }, maximized ? '还原' : '放大'),
|
|
1326
1346
|
React.createElement('button', { type: 'button', style: btnStyle, onClick: () => setPreview(null) }, '关闭'),
|
|
@@ -3014,6 +3034,259 @@ window.__ModuleLoader__.load({
|
|
|
3014
3034
|
},
|
|
3015
3035
|
}
|
|
3016
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
|
+
|
|
3017
3290
|
const inject = Array.from(new Set([
|
|
3018
3291
|
...uploadPlugin.inject,
|
|
3019
3292
|
...skillDocsPlugin.inject,
|
|
@@ -3021,6 +3294,7 @@ window.__ModuleLoader__.load({
|
|
|
3021
3294
|
...mobilePlugin.inject,
|
|
3022
3295
|
...workspaceFilesPlugin.inject,
|
|
3023
3296
|
...turnRulerPlugin.inject,
|
|
3297
|
+
...glassPlugin.inject,
|
|
3024
3298
|
]))
|
|
3025
3299
|
|
|
3026
3300
|
function apply(ctx) {
|
|
@@ -3037,6 +3311,7 @@ window.__ModuleLoader__.load({
|
|
|
3037
3311
|
safeApply('mobile-hamburger', (c) => mobilePlugin.apply(c))
|
|
3038
3312
|
safeApply('workspace-files', (c) => workspaceFilesPlugin.apply(c))
|
|
3039
3313
|
safeApply('turn-ruler', (c) => turnRulerPlugin.apply(c))
|
|
3314
|
+
safeApply('glass', (c) => glassPlugin.apply(c))
|
|
3040
3315
|
}
|
|
3041
3316
|
|
|
3042
3317
|
exports.apply = apply
|