dsh-long-plugins 1.3.4 → 1.3.6
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 +21 -32
- package/THIRD_PARTY_NOTICES.md +50 -0
- package/client/client.js +66 -15
- package/dsh.plugin.json +1 -1
- package/lib/index.js +116 -8
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,52 +1,28 @@
|
|
|
1
1
|
# dsh-long-plugins
|
|
2
2
|
|
|
3
3
|
Merged plugin bundle for [DeepSeek Harness](https://github.com/deepseek-ai/dsh) (DSH) Web:
|
|
4
|
-
upload manager, workspace **输出文件** section, top-bar **文件** browser with
|
|
5
|
-
**技能文档** (skill docs) browser,
|
|
4
|
+
upload manager, workspace **输出文件** section, top-bar **文件** browser with real **Word / PPT (docx / pptx) preview**,
|
|
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
|
|
8
|
+
一个插件整合 DSH Web 的常用增强:上传管理、顶栏「📂文件」工作区浏览 + 文件预览(Word、PPT 格式渲染)、工作区「输出文件」面板、技能文档浏览、账户余额显示。并自带修复安装,自动重连 DSH 核心补丁(反代 WebSocket 心跳、升级重连)。会话记录导航。
|
|
9
9
|
|
|
10
10
|
## Features / 功能
|
|
11
11
|
|
|
12
12
|
| Feature | 说明 |
|
|
13
13
|
|---|---|
|
|
14
|
-
| Upload manager |
|
|
15
|
-
| Workspace output files |
|
|
16
|
-
| Workspace file browser (顶栏「📂文件」) |
|
|
14
|
+
| Upload manager | 输入框回形针按钮上传本地文件,待发送文件栏管理,设置面板「上传文件」(预览/下载/删除、**按日期筛选**) |
|
|
15
|
+
| Workspace output files | 设置面板「输出文件」:按工作区文件夹分组、预览、**编辑/保存**、复制全部、**放大窗口**、**按日期筛选** |
|
|
16
|
+
| Workspace file browser (顶栏「📂文件」) | 顶栏「📂文件」打开工作区/全部文件浏览,按文件夹分组,**文件夹可点击折叠**;顶栏**日期选择**(选哪天只看那天的文件,含「今天/全部」快捷键);点文件名或「预览」在面板内预览,**Word (.docx) 用 docx-preview 浏览器端真实渲染(所见即所得)**;支持「←返回」「✕关闭」两层退出回列表,底部仅列表根时才整体退出;含下载/放大 |
|
|
17
|
+
| PowerPoint (.pptx) 预览 | 预览 `.pptx` 时用 **PptxViewJS 浏览器端真实渲染**(Canvas 逐页渲染、所见即所得),支持上一页/下一页翻页与**放大/缩小/适合宽度**;随包打包前端库 `client/vendor`(pptxviewjs / chart.js) |
|
|
17
18
|
| Skill docs | 设置面板「技能文档」:按技能目录浏览 SKILL.md、弹窗预览、编辑/保存、复制、放大窗口 |
|
|
18
19
|
| Account balance | 输入框下方显示 DeepSeek 账户余额(60s 自动刷新) |
|
|
19
20
|
| md2docx | DSH 工具:把 Markdown 一键转成带页码页脚的 Word(`.docx`),标题/表格/加粗/列表/引用,生成的文件作为可点击交付物出现在消息里 |
|
|
20
|
-
| Turn ruler |
|
|
21
|
+
| Turn ruler | 桌面端会话右侧 3 个比例刻度点,悬停打开「历史提问」预览窗;预览窗列出全部已加载轮次的标题,光标悬停点亮该行,点击行以蓝色标记并定位主会话;滚动到预览窗顶部自动加载更早历史(内容锚定恢复焦点,无震动、不跳跃),手机端触摸滚动已做性能优化;手机端右边缘竖向把手点击打开预览窗(居中弹窗,✕ 关闭) |
|
|
21
22
|
| Auto-repair install | 安装时自动重打 DSH 核心补丁:反代 WebSocket 心跳(修复提问窗口自己消失)、升级后重连 |
|
|
22
23
|
| Mobile layout | 手机端布局优化(输入栏、设置面板、主题选择、模型选择器) |
|
|
23
24
|
| Drag & drop upload | 拖任意文件到会话框直接附加为「待发送」卡片(走与回形针同一上传管线);全屏虚线投掷遮罩提示;消除 DSH 核心"仅支持 PNG/JPG/WebP/GIF 格式的图片"误报 |
|
|
24
25
|
|
|
25
|
-
## 更新记录 / Releases
|
|
26
|
-
|
|
27
|
-
### v1.3.2
|
|
28
|
-
|
|
29
|
-
**新增 · 顶栏「📂 文件」工作区浏览器 + Word 真实预览**:顶栏「📂文件」打开工作区/全部文件浏览,按文件夹分组;点文件名或「预览」在面板内预览。Word(`.docx`)走 `docx-preview` 浏览器端真实渲染(所见即所得,无需服务端转换),并随包打包前端库 `client/vendor`(docx-preview / jszip)。
|
|
30
|
-
|
|
31
|
-
**优化 · 两层关闭**:预览某文件时「✕关闭」/ Esc / 预览页自带「关闭」都回到文件列表,仅列表根部才整体退出面板;头部新增「←返回」按钮。Word 预览页也补上自带「✕关闭」,与 md 预览一致的两层关闭。
|
|
32
|
-
|
|
33
|
-
**新增 · PPT 真实预览(PptxViewJS)**:`.pptx` 也走浏览器端真实渲染(Canvas 逐页渲染、可翻页),与 docx 一致;随包打包 `client/vendor`(pptxviewjs / chart.js)。**优化 · 预览页固定顶栏**:Word / PPT / md 预览页顶部「下载 / ✕关闭」工具栏改为 sticky,滚动文档/幻灯片时不再被滚走。
|
|
34
|
-
|
|
35
|
-
### v1.3.0
|
|
36
|
-
|
|
37
|
-
**新增 · 拖放上传**:拖文件到网页任意位置松手即附加为「待发送」卡片;支持 `md / docx / xlsx / pptx / pdf / txt / csv` 及 `png / jpg / webp / gif`;与回形针同一条上传管线;拖放时全屏虚线投掷遮罩。
|
|
38
|
-
|
|
39
|
-
**🐛 修复**:消除核心"仅支持 PNG/JPG/WebP/GIF"误报——捕获阶段接管文件拖放。
|
|
40
|
-
|
|
41
|
-
**兼容性**:仅对真正拖入文件生效;拖文本/链接不受影响;标准浏览器事件,多设备通用。
|
|
42
|
-
|
|
43
|
-
## Turn ruler / 轮次导航
|
|
44
|
-
|
|
45
|
-
- 桌面端:会话右侧 3 个比例刻度点,悬停打开「历史提问」预览窗
|
|
46
|
-
- 预览窗:列出全部已加载轮次的标题;光标悬停点亮该行,点击行以蓝色标记并定位主会话
|
|
47
|
-
- 滚动到预览窗顶部自动加载更早历史(内容锚定恢复焦点,无震动、不跳跃);手机端触摸滚动已做性能优化
|
|
48
|
-
- 手机端:右边缘竖向把手点击打开预览窗(居中弹窗,✕ 关闭)
|
|
49
|
-
|
|
50
26
|
## Install / 安装
|
|
51
27
|
|
|
52
28
|
> **目录约定**:插件放 `$DSH_HOME/plugins/dsh-long-plugins`,DSH 运行脚本(`bin`)放 `$DSH_HOME/bin`,都别放工作目录。你的**工作目录**(名字任意)由用户指定,其下 `upload` 为上传根(`DSH_UPLOAD_DIR`)。目录缺失用 `mkdir -p` 补建。
|
|
@@ -129,3 +105,16 @@ Plus the upload manager's core routes (`/api/dsh-uploads`, download, preview).
|
|
|
129
105
|
## License
|
|
130
106
|
|
|
131
107
|
MIT
|
|
108
|
+
|
|
109
|
+
## Third-party libraries / 第三方开源库
|
|
110
|
+
|
|
111
|
+
本插件在浏览器端打包了以下开源库用于文件预览:
|
|
112
|
+
|
|
113
|
+
- `docx-preview`(docxjs) — Word 真实预览 — **Apache-2.0**
|
|
114
|
+
- `JSZip` — 解压 Office 包 — **MIT**(或 GPLv3,本项目按 MIT 使用)
|
|
115
|
+
- `Chart.js` — PPT 内图表 — **MIT**
|
|
116
|
+
- `PptxViewJS` — PowerPoint 真实预览 — **MIT**
|
|
117
|
+
- npm 依赖:`mammoth`(BSD-2-Clause)、`exceljs`(MIT)
|
|
118
|
+
|
|
119
|
+
完整的版权声明与来源见 [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)。
|
|
120
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Third-party notices / 第三方开源库声明
|
|
2
|
+
|
|
3
|
+
This plugin bundles the following third-party libraries (in `client/vendor/`) and
|
|
4
|
+
depends on the following npm packages. Each is used under its own license; the
|
|
5
|
+
copyright notices and sources are listed here.
|
|
6
|
+
|
|
7
|
+
## Bundled libraries — `client/vendor/`
|
|
8
|
+
|
|
9
|
+
### docx-preview (docxjs)
|
|
10
|
+
- Used for: real browser-side Word (`.docx`) preview
|
|
11
|
+
- License: **Apache License 2.0**
|
|
12
|
+
- Copyright: Volodymyr Baydalka
|
|
13
|
+
- Source: <https://github.com/VolodymyrBaydalka/docxjs>
|
|
14
|
+
- License text: <https://www.apache.org/licenses/LICENSE-2.0>
|
|
15
|
+
|
|
16
|
+
### JSZip
|
|
17
|
+
- Used for: unzipping the OOXML/Office package parts
|
|
18
|
+
- License: **MIT** (dual-licensed MIT or GPLv3; this project uses it under MIT)
|
|
19
|
+
- Copyright: 2009-2016 Stuart Knightley
|
|
20
|
+
- Source: <https://github.com/Stuk/jszip>
|
|
21
|
+
- License text: <https://github.com/Stuk/jszip/blob/main/LICENSE.markdown>
|
|
22
|
+
|
|
23
|
+
### Chart.js
|
|
24
|
+
- Used for: charts rendered inside the PowerPoint (`.pptx`) preview
|
|
25
|
+
- Version: 4.4.1
|
|
26
|
+
- License: **MIT**
|
|
27
|
+
- Copyright: 2023 Chart.js Contributors
|
|
28
|
+
- Source: <https://www.chartjs.org>
|
|
29
|
+
|
|
30
|
+
### PptxViewJS (`pptxviewjs`)
|
|
31
|
+
- Used for: real browser-side PowerPoint (`.pptx`) preview
|
|
32
|
+
- License: **MIT**
|
|
33
|
+
- Copyright: © 2025 gptsci.com (npm author: Alex Wong)
|
|
34
|
+
- npm: <https://www.npmjs.com/package/pptxviewjs>
|
|
35
|
+
- CDN / source build: <https://cdn.jsdelivr.net/npm/pptxviewjs@1.1.9>
|
|
36
|
+
|
|
37
|
+
## Runtime npm dependencies (not vendored into the package)
|
|
38
|
+
|
|
39
|
+
These are declared as `dependencies` and resolved on the host at install time;
|
|
40
|
+
their own `LICENSE` files ship inside `node_modules`.
|
|
41
|
+
|
|
42
|
+
- `mammoth` — **BSD-2-Clause** (Word → HTML preview)
|
|
43
|
+
- `exceljs` — **MIT** (spreadsheet parsing)
|
|
44
|
+
|
|
45
|
+
## Note
|
|
46
|
+
|
|
47
|
+
MIT, Apache-2.0 and BSD-2-Clause licenses require retaining the copyright notice
|
|
48
|
+
and license text. The minified vendor files `docx-preview.min.js`,
|
|
49
|
+
`jszip.min.js`, and `chart.umd.min.js` already carry their license banners at
|
|
50
|
+
the top of the files. The full license texts are available at the sources above.
|
package/client/client.js
CHANGED
|
@@ -566,6 +566,40 @@ window.__ModuleLoader__.load({
|
|
|
566
566
|
}
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
+
/** 本地时区 YYYY-MM-DD(日期筛选/分组用,避免 toISOString 的 UTC 漂移)。 */
|
|
570
|
+
const localDay = (ts) => {
|
|
571
|
+
const d = new Date(ts)
|
|
572
|
+
const p = (v) => v < 10 ? '0' + v : v
|
|
573
|
+
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate())
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** 自定义日期筛选控件:显示 📅 + 文字,点击/触摸打开系统日期选择器,自带 ✕ 清除。
|
|
577
|
+
* 原生 input[type=date] 在手机端是空框、无图标;此处整个控件包一层 onClick,
|
|
578
|
+
* 统一调用 input.showPicker(),故点图标/文字/任意位置都触发(桌面/手机一致)。 */
|
|
579
|
+
function DateFilter({ value, onChange, placeholder }) {
|
|
580
|
+
const ref = React.useRef(null)
|
|
581
|
+
const label = value || placeholder || '选择日期'
|
|
582
|
+
const open = () => {
|
|
583
|
+
const el = ref.current
|
|
584
|
+
if (!el) return
|
|
585
|
+
if (typeof el.showPicker === 'function') { try { el.showPicker(); return } catch (e) {} }
|
|
586
|
+
try { el.focus() } catch (e) {}
|
|
587
|
+
}
|
|
588
|
+
return React.createElement('div', { className: 'dsh-datefilter', onClick: open },
|
|
589
|
+
React.createElement('span', { className: 'dsh-datefilter-icon' }, '📅'),
|
|
590
|
+
React.createElement('span', { className: 'dsh-datefilter-text' }, label),
|
|
591
|
+
value !== '' && React.createElement('button', {
|
|
592
|
+
type: 'button', className: 'dsh-datefilter-clear', title: '清除', 'aria-label': '清除日期',
|
|
593
|
+
onClick: (e) => { e.stopPropagation(); onChange('') },
|
|
594
|
+
}, '✕'),
|
|
595
|
+
React.createElement('input', {
|
|
596
|
+
ref, type: 'date', className: 'dsh-datefilter-native', value,
|
|
597
|
+
onChange: (e) => onChange(e.target.value),
|
|
598
|
+
tabIndex: -1, 'aria-label': placeholder || '选择日期',
|
|
599
|
+
}),
|
|
600
|
+
)
|
|
601
|
+
}
|
|
602
|
+
|
|
569
603
|
function UploadSettingsSection() {
|
|
570
604
|
const [state, setState] = React.useState({
|
|
571
605
|
loading: true,
|
|
@@ -579,7 +613,7 @@ window.__ModuleLoader__.load({
|
|
|
579
613
|
const [deleting, setDeleting] = React.useState('')
|
|
580
614
|
const [preview, setPreview] = React.useState(null)
|
|
581
615
|
const [previewMaximized, setPreviewMaximized] = React.useState(false)
|
|
582
|
-
const [
|
|
616
|
+
const [dayFilter, setDayFilter] = React.useState('')
|
|
583
617
|
|
|
584
618
|
async function refresh() {
|
|
585
619
|
setState((current) => ({ ...current, loading: true, error: '' }))
|
|
@@ -675,7 +709,7 @@ window.__ModuleLoader__.load({
|
|
|
675
709
|
for (const file of files) {
|
|
676
710
|
let day = ''
|
|
677
711
|
try {
|
|
678
|
-
day =
|
|
712
|
+
day = localDay(file.modifiedAt)
|
|
679
713
|
} catch {
|
|
680
714
|
day = '未知日期'
|
|
681
715
|
}
|
|
@@ -686,7 +720,11 @@ window.__ModuleLoader__.load({
|
|
|
686
720
|
for (const day of days) groups.push({ day, files: byDay.get(day) })
|
|
687
721
|
return groups
|
|
688
722
|
}
|
|
689
|
-
|
|
723
|
+
// 日期筛选:先按所选天过滤 state.files,再始终按天分组(空日期=全部)。
|
|
724
|
+
const shownFiles = dayFilter
|
|
725
|
+
? state.files.filter((f) => { try { return localDay(f.modifiedAt) === dayFilter } catch { return false } })
|
|
726
|
+
: state.files
|
|
727
|
+
const dateGroups = groupFilesByDate(shownFiles)
|
|
690
728
|
|
|
691
729
|
return React.createElement(
|
|
692
730
|
'section',
|
|
@@ -703,16 +741,7 @@ window.__ModuleLoader__.load({
|
|
|
703
741
|
React.createElement(
|
|
704
742
|
'div',
|
|
705
743
|
{ className: 'dsh-upload-head-actions' },
|
|
706
|
-
React.createElement(
|
|
707
|
-
'button',
|
|
708
|
-
{
|
|
709
|
-
type: 'button',
|
|
710
|
-
className: 'dsh-upload-refresh',
|
|
711
|
-
disabled: state.loading,
|
|
712
|
-
onClick: () => setGroupByDate((g) => !g),
|
|
713
|
-
},
|
|
714
|
-
groupByDate ? '日期 ✓' : '日期',
|
|
715
|
-
),
|
|
744
|
+
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
716
745
|
React.createElement(
|
|
717
746
|
'button',
|
|
718
747
|
{ type: 'button', className: 'dsh-upload-refresh', disabled: state.loading, onClick: refresh },
|
|
@@ -736,6 +765,9 @@ window.__ModuleLoader__.load({
|
|
|
736
765
|
!state.loading && state.files.length === 0
|
|
737
766
|
? React.createElement('div', { className: 'dsh-upload-empty' }, '当前没有已上传文件。')
|
|
738
767
|
: null,
|
|
768
|
+
!state.loading && dayFilter !== '' && shownFiles.length === 0 && state.files.length > 0
|
|
769
|
+
? React.createElement('div', { className: 'dsh-upload-empty' }, '该日期没有文件。')
|
|
770
|
+
: null,
|
|
739
771
|
preview
|
|
740
772
|
? React.createElement(
|
|
741
773
|
'div',
|
|
@@ -780,7 +812,7 @@ window.__ModuleLoader__.load({
|
|
|
780
812
|
React.createElement(
|
|
781
813
|
'div',
|
|
782
814
|
{ className: 'dsh-upload-list' },
|
|
783
|
-
(dateGroups === null ? [{ day: null, files:
|
|
815
|
+
(dateGroups === null ? [{ day: null, files: shownFiles }] : dateGroups).map((group) => React.createElement(
|
|
784
816
|
'div',
|
|
785
817
|
{ key: group.day ?? '__all__', className: 'dsh-upload-group' },
|
|
786
818
|
group.day !== null
|
|
@@ -844,6 +876,13 @@ window.__ModuleLoader__.load({
|
|
|
844
876
|
.dsh-upload-settings h2{margin:0;font-size:20px;line-height:28px}
|
|
845
877
|
.dsh-upload-settings p{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
846
878
|
.dsh-upload-refresh,.dsh-upload-actions button,.dsh-upload-actions a{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);padding:6px 11px;font:inherit;font-size:12px;line-height:18px;text-decoration:none;cursor:pointer}
|
|
879
|
+
.dsh-datefilter{position:relative;display:inline-flex;align-items:center;gap:6px;min-width:118px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;padding:5px 8px;color:var(--dsw-alias-label-primary);font:inherit;font-size:12px;line-height:18px;cursor:pointer}
|
|
880
|
+
.dsh-datefilter:hover{border-color:var(--dsw-alias-state-business-primary)}
|
|
881
|
+
.dsh-datefilter-icon{font-size:13px;flex:none}
|
|
882
|
+
.dsh-datefilter-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
883
|
+
.dsh-datefilter-native{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;pointer-events:auto;z-index:1}
|
|
884
|
+
.dsh-datefilter-clear{position:relative;z-index:2;background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1;cursor:pointer;padding:0 2px}
|
|
885
|
+
.dsh-datefilter-clear:hover{color:var(--dsw-alias-label-primary)}
|
|
847
886
|
.dsh-upload-refresh:hover:not(:disabled),.dsh-upload-actions button:hover:not(:disabled),.dsh-upload-actions a:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
848
887
|
.dsh-upload-root{display:grid;grid-template-columns:auto minmax(0,1fr);gap:5px 12px;align-items:center;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:10px;background:var(--dsw-specific-input-major)}
|
|
849
888
|
.dsh-upload-root span{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
@@ -901,6 +940,7 @@ window.__ModuleLoader__.load({
|
|
|
901
940
|
const [preview, setPreview] = React.useState(null)
|
|
902
941
|
const [busy, setBusy] = React.useState(false)
|
|
903
942
|
const [collapsed, setCollapsed] = React.useState({})
|
|
943
|
+
const [dayFilter, setDayFilter] = React.useState('')
|
|
904
944
|
const [copied, setCopied] = React.useState(false)
|
|
905
945
|
const [editing, setEditing] = React.useState(false)
|
|
906
946
|
const [edited, setEdited] = React.useState('')
|
|
@@ -1083,14 +1123,25 @@ window.__ModuleLoader__.load({
|
|
|
1083
1123
|
padding: '10px 14px', borderBottom: '1px solid var(--dsw-alias-border-l2)', flex: 'none',
|
|
1084
1124
|
}
|
|
1085
1125
|
|
|
1126
|
+
// 日期筛选:按所选天过滤各组文件,隐藏空组(空日期=全部)。
|
|
1127
|
+
const shownGroups = (dayFilter && groups !== null)
|
|
1128
|
+
? groups
|
|
1129
|
+
.map((g) => ({ folder: g.folder, files: g.files.filter((f) => { try { return localDay(f.mtime) === dayFilter } catch { return false } }) }))
|
|
1130
|
+
.filter((g) => g.files.length > 0)
|
|
1131
|
+
: groups
|
|
1132
|
+
|
|
1086
1133
|
return React.createElement(
|
|
1087
1134
|
'div',
|
|
1088
1135
|
{ style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
1089
1136
|
React.createElement('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-tertiary)' } }, '工作区输出文件(按文件夹分类,预览 / 下载 / 删除)'),
|
|
1137
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', minWidth: 0 } },
|
|
1138
|
+
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
1139
|
+
),
|
|
1090
1140
|
error !== null && React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, error),
|
|
1091
1141
|
groups === null && error === null && React.createElement('div', { style: metaStyle }, '加载中…'),
|
|
1092
1142
|
groups !== null && groups.length === 0 && React.createElement('div', { style: metaStyle }, '目录为空'),
|
|
1093
|
-
groups !== null && groups.
|
|
1143
|
+
dayFilter && groups !== null && groups.length > 0 && shownGroups !== null && shownGroups.length === 0 && React.createElement('div', { style: metaStyle }, '该日期没有文件'),
|
|
1144
|
+
shownGroups !== null && shownGroups.map((group) => React.createElement(
|
|
1094
1145
|
'div',
|
|
1095
1146
|
{ key: group.folder, style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
1096
1147
|
React.createElement(
|
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": "1.3.
|
|
4
|
+
"version": "1.3.6",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -1119,6 +1119,13 @@ window.addEventListener('resize', function(){ try{ if(fitMode) zoomFit(); else a
|
|
|
1119
1119
|
#stage canvas{display:block;margin:0 auto;background:#fff;border-radius:8px;box-shadow:0 4px 22px rgba(0,0,0,.45)}
|
|
1120
1120
|
#status{font-size:12px;color:#9ca3af;min-width:70px;text-align:center}
|
|
1121
1121
|
#msg{color:#9ca3af}
|
|
1122
|
+
/* 电脑预览器式左右翻页箭头:浮在幻灯片两侧 */
|
|
1123
|
+
.pv-arrow{position:fixed;top:50%;transform:translateY(-50%);z-index:30;width:48px;height:48px;border-radius:50%;border:1px solid rgba(255,255,255,.28);background:rgba(18,28,42,.55);color:#fff;font-size:26px;line-height:1;display:flex;align-items:center;justify-content:center;cursor:pointer;user-select:none}
|
|
1124
|
+
.pv-arrow:hover{background:rgba(45,66,95,.85)}
|
|
1125
|
+
.pv-arrow:disabled{opacity:.28;cursor:default}
|
|
1126
|
+
.pv-prev{left:10px}
|
|
1127
|
+
.pv-next{right:10px}
|
|
1128
|
+
@media (max-width:767px){ .pv-arrow{width:40px;height:40px;font-size:22px} }
|
|
1122
1129
|
</style>
|
|
1123
1130
|
</head><body>
|
|
1124
1131
|
<div class="bar">
|
|
@@ -1135,6 +1142,8 @@ window.addEventListener('resize', function(){ try{ if(fitMode) zoomFit(); else a
|
|
|
1135
1142
|
<button type="button" onclick="closePreview()">✕ 关闭</button>
|
|
1136
1143
|
</div>
|
|
1137
1144
|
<div id="stage"><div id="msg">加载中…</div><canvas id="canvas" style="display:none"></canvas></div>
|
|
1145
|
+
<button type="button" class="pv-arrow pv-prev" id="prevOverlay" title="上一页" disabled>‹</button>
|
|
1146
|
+
<button type="button" class="pv-arrow pv-next" id="nextOverlay" title="下一页" disabled>›</button>
|
|
1138
1147
|
<script src="${assetBase}?f=jszip.min.js"></script>
|
|
1139
1148
|
<script src="${assetBase}?f=chart.umd.min.js"></script>
|
|
1140
1149
|
<script src="${assetBase}?f=pptxviewjs.min.js"></script>
|
|
@@ -1149,6 +1158,8 @@ const stage=document.getElementById('stage');
|
|
|
1149
1158
|
const msg=document.getElementById('msg');
|
|
1150
1159
|
const prevBtn=document.getElementById('prev');
|
|
1151
1160
|
const nextBtn=document.getElementById('next');
|
|
1161
|
+
const prevOverlay=document.getElementById('prevOverlay');
|
|
1162
|
+
const nextOverlay=document.getElementById('nextOverlay');
|
|
1152
1163
|
const status=document.getElementById('status');
|
|
1153
1164
|
let viewer=null,total=0;
|
|
1154
1165
|
var zoom=1, fitMode=true;
|
|
@@ -1160,7 +1171,11 @@ function applyZoom(){
|
|
|
1160
1171
|
canvas.style.height=Math.round(nh*zoom)+'px';
|
|
1161
1172
|
}
|
|
1162
1173
|
function zoomFit(){
|
|
1163
|
-
var nw=canvas.width||1280;
|
|
1174
|
+
var nw=canvas.width||1280;
|
|
1175
|
+
// 用「内容区宽度」(clientWidth - 左右 padding),否则 canvas 会多出 padding 宽度、造成横向溢出,
|
|
1176
|
+
// 拖到手势里「有溢出=已放大」的误判,导致滑动翻页失效。
|
|
1177
|
+
var cs=getComputedStyle(stage); var pl=parseFloat(cs.paddingLeft)||0, pr=parseFloat(cs.paddingRight)||0;
|
|
1178
|
+
var t=(stage.clientWidth-pl-pr)||1;
|
|
1164
1179
|
zoom=Math.min(1, t/nw); fitMode=true; applyZoom();
|
|
1165
1180
|
}
|
|
1166
1181
|
function zoom100(){ zoom=1; fitMode=false; applyZoom(); }
|
|
@@ -1169,8 +1184,8 @@ function update(){
|
|
|
1169
1184
|
if(!viewer)return;
|
|
1170
1185
|
const cur=viewer.getCurrentSlideIndex();
|
|
1171
1186
|
status.textContent='第 '+(cur+1)+' / '+total+' 页';
|
|
1172
|
-
prevBtn.disabled=cur<=0;
|
|
1173
|
-
nextBtn.disabled=cur>=total-1;
|
|
1187
|
+
prevBtn.disabled=cur<=0; prevOverlay.disabled=cur<=0;
|
|
1188
|
+
nextBtn.disabled=cur>=total-1; nextOverlay.disabled=cur>=total-1;
|
|
1174
1189
|
}
|
|
1175
1190
|
(async()=>{
|
|
1176
1191
|
try{
|
|
@@ -1195,6 +1210,8 @@ function update(){
|
|
|
1195
1210
|
})();
|
|
1196
1211
|
prevBtn.addEventListener('click',async()=>{if(viewer){await viewer.previousSlide();update();}});
|
|
1197
1212
|
nextBtn.addEventListener('click',async()=>{if(viewer){await viewer.nextSlide();update();}});
|
|
1213
|
+
prevOverlay.addEventListener('click',async()=>{if(viewer){await viewer.previousSlide();update();}});
|
|
1214
|
+
nextOverlay.addEventListener('click',async()=>{if(viewer){await viewer.nextSlide();update();}});
|
|
1198
1215
|
window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else applyZoom(); }catch(e){} });
|
|
1199
1216
|
</script>
|
|
1200
1217
|
</body></html>`);
|
|
@@ -1263,7 +1280,7 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1263
1280
|
const viewHref = previewable
|
|
1264
1281
|
? (isPdf ? `workspace-file?path=${rel}&inline=1` : isDocx ? `docx-preview?path=${rel}` : isPptx ? `pptx-preview?path=${rel}` : `workspace-preview?path=${rel}&from=list`)
|
|
1265
1282
|
: `workspace-file?path=${rel}&download=1`;
|
|
1266
|
-
return `<tr>
|
|
1283
|
+
return `<tr data-ts="${Math.floor(f.mtime)}">
|
|
1267
1284
|
<td class="name"><a class="flink" href="${viewHref}" data-preview="${escapeHtml(viewHref)}" title="${escapeHtml(f.path)}">${escapeHtml(f.name)}</a></td>
|
|
1268
1285
|
<td class="size">${humanSize(f.size)}</td>
|
|
1269
1286
|
<td class="time">${humanTime(f.mtime)}</td>
|
|
@@ -1273,7 +1290,7 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1273
1290
|
</td>
|
|
1274
1291
|
</tr>`;
|
|
1275
1292
|
}).join("");
|
|
1276
|
-
return `<div class="group"><h2>${escapeHtml(group.folder)} <span class="cnt">${group.files.length}</span></h2>
|
|
1293
|
+
return `<div class="group"><h2 class="gtoggle">${escapeHtml(group.folder)} <span class="cnt">${group.files.length}</span></h2>
|
|
1277
1294
|
<table><thead><tr><th>文件</th><th>大小</th><th>修改</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div>`;
|
|
1278
1295
|
};
|
|
1279
1296
|
return `<!DOCTYPE html>
|
|
@@ -1317,6 +1334,24 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1317
1334
|
.group { margin-bottom:26px; }
|
|
1318
1335
|
.group h2 { font-size:15px; margin:0 0 8px; color:var(--lp-h2); }
|
|
1319
1336
|
.cnt { color:var(--lp-meta); font-size:12px; font-weight:400; }
|
|
1337
|
+
.gtoggle { cursor:pointer; user-select:none; display:inline-flex; align-items:center; gap:8px; }
|
|
1338
|
+
.gtoggle:before { content:'▾'; font-size:11px; color:var(--lp-meta); transition:transform .15s; }
|
|
1339
|
+
.group.collapsed .gtoggle:before { content:'▸'; }
|
|
1340
|
+
.group.collapsed table { display:none; }
|
|
1341
|
+
.group.filtered-empty { display:none; }
|
|
1342
|
+
.daybox { display:inline-flex; align-items:center; gap:8px; flex:none; }
|
|
1343
|
+
.daylabel { color:var(--lp-meta); font-size:13px; }
|
|
1344
|
+
.dfilter { position:relative; display:inline-flex; align-items:center; gap:6px; min-width:118px; border:1px solid var(--lp-border); border-radius:8px; background:var(--lp-btn-bg); color:var(--lp-fg); padding:5px 8px; font-size:13px; cursor:pointer; }
|
|
1345
|
+
.dfilter:hover { border-color:var(--lp-accent); }
|
|
1346
|
+
.dfilter.has-value { border-color:var(--lp-accent); }
|
|
1347
|
+
.dfilter-icon { font-size:13px; flex:none; }
|
|
1348
|
+
.dfilter-text { white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
1349
|
+
.dfilter-clear { position:relative; z-index:2; background:transparent; border:none; color:var(--lp-meta); font-size:12px; line-height:1; cursor:pointer; padding:0 2px; }
|
|
1350
|
+
.dfilter-clear:hover { color:var(--lp-fg); }
|
|
1351
|
+
.dfilter-native { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; pointer-events:auto; z-index:1; }
|
|
1352
|
+
.btn2 { display:inline-flex; align-items:center; background:var(--lp-btn-bg); color:var(--lp-btn-fg); border:1px solid var(--lp-border); border-radius:8px; padding:6px 11px; font-size:12px; cursor:pointer; }
|
|
1353
|
+
.btn2:hover { background:var(--lp-btn-hover); }
|
|
1354
|
+
.daybtn.active { background:#2563eb; color:#fff; }
|
|
1320
1355
|
table { width:100%; border-collapse:collapse; background:var(--lp-table-bg); border:1px solid var(--lp-table-border); border-radius:10px; overflow:hidden; }
|
|
1321
1356
|
th, td { text-align:left; padding:9px 14px; font-size:13px; border-bottom:1px solid var(--lp-cell-border); }
|
|
1322
1357
|
th { background:var(--lp-th-bg); color:var(--lp-meta); font-weight:500; }
|
|
@@ -1355,11 +1390,22 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1355
1390
|
<a class="${all ? "on" : ""}" href="${allHref}">${ICON_FOLDER_OPEN} 总文件</a>
|
|
1356
1391
|
</div>
|
|
1357
1392
|
<span class="meta">${metaText}</span>
|
|
1393
|
+
<span class="daybox">
|
|
1394
|
+
<label class="daylabel" for="dayFilter">日期</label>
|
|
1395
|
+
<span class="dfilter" id="dfilter">
|
|
1396
|
+
<span class="dfilter-icon">📅</span>
|
|
1397
|
+
<span class="dfilter-text" id="dfilterText">选择日期</span>
|
|
1398
|
+
<button type="button" class="dfilter-clear" id="dfilterClear" title="清除" aria-label="清除日期" style="display:none">✕</button>
|
|
1399
|
+
<input type="date" id="dayFilter" class="dfilter-native" aria-label="按日期筛选">
|
|
1400
|
+
</span>
|
|
1401
|
+
<button type="button" class="btn2 daybtn" id="dayToday">今天</button>
|
|
1402
|
+
</span>
|
|
1358
1403
|
<span class="spacer"></span>
|
|
1359
1404
|
<a class="btn" href="${refreshHref}">⟳ 刷新</a>
|
|
1360
1405
|
</div>
|
|
1361
1406
|
<div class="wrap">
|
|
1362
1407
|
${groups.map(section).join("") || `<p class="empty">${ws && !all ? `工作区「${escapeHtml(ws)}」还没有文件` : "工作区还没有文件"}</p>`}
|
|
1408
|
+
<p class="empty" id="filterEmpty" style="display:none">该日期没有文件</p>
|
|
1363
1409
|
</div>
|
|
1364
1410
|
<script>
|
|
1365
1411
|
// 预览链接点击 → 通知父窗口(wsOverlay)打开,父窗口用 embed/iframe 渲染,
|
|
@@ -1381,6 +1427,68 @@ document.addEventListener('click', function (e) {
|
|
|
1381
1427
|
} catch (err) { /* 跨域忽略 */ }
|
|
1382
1428
|
location.href = abs;
|
|
1383
1429
|
});
|
|
1430
|
+
// 按文件夹可折叠 + 按日期筛选(前端 JS;mtime 以浏览器本地时区算日期)
|
|
1431
|
+
(function () {
|
|
1432
|
+
var groups = Array.prototype.slice.call(document.querySelectorAll('.group'));
|
|
1433
|
+
// 1) 文件夹头点击 → 折叠/展开
|
|
1434
|
+
groups.forEach(function (g) {
|
|
1435
|
+
var h = g.querySelector('.gtoggle');
|
|
1436
|
+
if (h) h.addEventListener('click', function () { g.classList.toggle('collapsed'); });
|
|
1437
|
+
});
|
|
1438
|
+
// 2) 日期筛选
|
|
1439
|
+
var dayInput = document.getElementById('dayFilter');
|
|
1440
|
+
var dayToday = document.getElementById('dayToday');
|
|
1441
|
+
var dfilter = document.getElementById('dfilter');
|
|
1442
|
+
var dfilterText = document.getElementById('dfilterText');
|
|
1443
|
+
var dfilterClear = document.getElementById('dfilterClear');
|
|
1444
|
+
var filterEmpty = document.getElementById('filterEmpty');
|
|
1445
|
+
var metaEl = document.querySelector('.bar .meta');
|
|
1446
|
+
var origMeta = metaEl ? metaEl.textContent : '';
|
|
1447
|
+
function pad(v) { return v < 10 ? '0' + v : v; }
|
|
1448
|
+
function fmtDate(ts) {
|
|
1449
|
+
var d = new Date(ts);
|
|
1450
|
+
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
|
1451
|
+
}
|
|
1452
|
+
// 预计算每行本地日期
|
|
1453
|
+
Array.prototype.forEach.call(document.querySelectorAll('tr[data-ts]'), function (tr) {
|
|
1454
|
+
tr.dataset.date = fmtDate(Number(tr.dataset.ts));
|
|
1455
|
+
});
|
|
1456
|
+
var todayStr = fmtDate(Date.now());
|
|
1457
|
+
function apply() {
|
|
1458
|
+
var sel = dayInput.value;
|
|
1459
|
+
var visTotal = 0;
|
|
1460
|
+
groups.forEach(function (g) {
|
|
1461
|
+
var vis = 0;
|
|
1462
|
+
Array.prototype.forEach.call(g.querySelectorAll('tr[data-date]'), function (tr) {
|
|
1463
|
+
var ok = !sel || tr.dataset.date === sel;
|
|
1464
|
+
tr.style.display = ok ? '' : 'none';
|
|
1465
|
+
if (ok) vis++;
|
|
1466
|
+
});
|
|
1467
|
+
var cnt = g.querySelector('.cnt');
|
|
1468
|
+
if (cnt) cnt.textContent = vis;
|
|
1469
|
+
if (vis === 0) g.classList.add('filtered-empty'); else g.classList.remove('filtered-empty');
|
|
1470
|
+
visTotal += vis;
|
|
1471
|
+
});
|
|
1472
|
+
if (metaEl) metaEl.textContent = sel ? ('筛选 ' + sel + ' · ' + visTotal + ' 个文件') : origMeta;
|
|
1473
|
+
if (dfilterText) dfilterText.textContent = sel || '选择日期';
|
|
1474
|
+
if (dfilterClear) dfilterClear.style.display = sel ? 'inline-block' : 'none';
|
|
1475
|
+
if (dfilter) dfilter.classList.toggle('has-value', !!sel);
|
|
1476
|
+
if (dayToday) dayToday.classList.toggle('active', sel === todayStr);
|
|
1477
|
+
if (filterEmpty) filterEmpty.style.display = (sel && visTotal === 0) ? 'block' : 'none';
|
|
1478
|
+
}
|
|
1479
|
+
// 打开日期选择器:整个控件点击 → showPicker(兜底 focus);点 ✕ 清除(阻止冒泡)。
|
|
1480
|
+
if (dfilter) dfilter.addEventListener('click', function () {
|
|
1481
|
+
var el = dayInput;
|
|
1482
|
+
if (!el) return;
|
|
1483
|
+
if (typeof el.showPicker === 'function') { try { el.showPicker(); return; } catch (e) {} }
|
|
1484
|
+
try { el.focus(); } catch (e) {}
|
|
1485
|
+
});
|
|
1486
|
+
if (dayInput) dayInput.addEventListener('input', apply);
|
|
1487
|
+
if (dayInput) dayInput.addEventListener('change', apply);
|
|
1488
|
+
if (dayToday) dayToday.addEventListener('click', function () { dayInput.value = todayStr; apply(); });
|
|
1489
|
+
if (dfilterClear) dfilterClear.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); dayInput.value = ''; apply(); });
|
|
1490
|
+
apply();
|
|
1491
|
+
})();
|
|
1384
1492
|
</script>
|
|
1385
1493
|
</body>
|
|
1386
1494
|
</html>`;
|
|
@@ -1719,8 +1827,8 @@ export async function apply(ctx, config = {}) {
|
|
|
1719
1827
|
// 默认脚本随插件包分发(lib/md2docx.py),换机器也可靠;可用 config.md2docxScript 覆盖。
|
|
1720
1828
|
const PACKAGE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
1721
1829
|
const MD2DOCX_SCRIPT = resolve(config.md2docxScript ?? join(PACKAGE_DIR, "md2docx.py"));
|
|
1722
|
-
const runScript = (args) => new Promise((resolvePromise) => {
|
|
1723
|
-
const child = spawn("python3", [
|
|
1830
|
+
const runScript = (script, args) => new Promise((resolvePromise) => {
|
|
1831
|
+
const child = spawn("python3", [script, ...args], { stdio: ["ignore", "pipe", "pipe"] });
|
|
1724
1832
|
let stdout = "", stderr = "";
|
|
1725
1833
|
child.stdout.on("data", (d) => { stdout += d.toString(); });
|
|
1726
1834
|
child.stderr.on("data", (d) => { stderr += d.toString(); });
|
|
@@ -1773,7 +1881,7 @@ export async function apply(ctx, config = {}) {
|
|
|
1773
1881
|
async execute(args) {
|
|
1774
1882
|
const inPath = resolve(String(args.input ?? ""));
|
|
1775
1883
|
const outPath = args.output ? resolve(String(args.output)) : inPath.replace(/\.md$/i, ".docx");
|
|
1776
|
-
const result = await runScript([inPath, outPath]);
|
|
1884
|
+
const result = await runScript(MD2DOCX_SCRIPT, [inPath, outPath]);
|
|
1777
1885
|
if (!result.ok) {
|
|
1778
1886
|
return { ok: false, error: (result.stderr || result.stdout || String(result.error)).trim() || `md2docx failed (exit ${result.code})` };
|
|
1779
1887
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
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",
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"!client/*.bak",
|
|
18
18
|
"cordis.patch.yml",
|
|
19
19
|
"dsh.plugin.json",
|
|
20
|
+
"THIRD_PARTY_NOTICES.md",
|
|
20
21
|
"skill/dsh-long-plugins-install/SKILL.md",
|
|
21
22
|
"skill/dsh-upgrade/SKILL.md",
|
|
22
23
|
"skill/dsh-common-plugins-install/SKILL.md",
|