dsh-long-plugins 1.3.5 → 1.3.7
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 +16 -3
- package/THIRD_PARTY_NOTICES.md +50 -0
- package/client/client.js +186 -37
- package/dsh.plugin.json +1 -1
- package/lib/index.js +147 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -11,9 +11,9 @@ Also ships an auto-repair install that relinks DSH core patches (reverse-proxy W
|
|
|
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
17
|
| PowerPoint (.pptx) 预览 | 预览 `.pptx` 时用 **PptxViewJS 浏览器端真实渲染**(Canvas 逐页渲染、所见即所得),支持上一页/下一页翻页与**放大/缩小/适合宽度**;随包打包前端库 `client/vendor`(pptxviewjs / chart.js) |
|
|
18
18
|
| Skill docs | 设置面板「技能文档」:按技能目录浏览 SKILL.md、弹窗预览、编辑/保存、复制、放大窗口 |
|
|
19
19
|
| Account balance | 输入框下方显示 DeepSeek 账户余额(60s 自动刷新) |
|
|
@@ -105,3 +105,16 @@ Plus the upload manager's core routes (`/api/dsh-uploads`, download, preview).
|
|
|
105
105
|
## License
|
|
106
106
|
|
|
107
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,81 @@ 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
|
+
const matchesSearch = (file, search) => {
|
|
578
|
+
const q = String(search || '').trim().toLowerCase()
|
|
579
|
+
if (!q) return true
|
|
580
|
+
const hay = ((file.name || '') + ' ' + (file.path || '')).toLowerCase()
|
|
581
|
+
return q.split(/\s+/).filter(Boolean).every((tok) => hay.indexOf(tok) !== -1)
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** 自定义日期筛选控件:显示 📅 + 文字,点击/触摸打开系统日期选择器,自带 ✕ 清除。
|
|
585
|
+
* 原生 input[type=date] 在手机端是空框、无图标;此处整个控件包一层 onClick,
|
|
586
|
+
* 统一调用 input.showPicker(),故点图标/文字/任意位置都触发(桌面/手机一致)。 */
|
|
587
|
+
function DateFilter({ value, onChange, placeholder }) {
|
|
588
|
+
const ref = React.useRef(null)
|
|
589
|
+
const label = value || placeholder || '选择日期'
|
|
590
|
+
const open = () => {
|
|
591
|
+
const el = ref.current
|
|
592
|
+
if (!el) return
|
|
593
|
+
if (typeof el.showPicker === 'function') { try { el.showPicker(); return } catch (e) {} }
|
|
594
|
+
try { el.focus() } catch (e) {}
|
|
595
|
+
}
|
|
596
|
+
return React.createElement('div', { className: 'dsh-datefilter', onClick: open },
|
|
597
|
+
React.createElement('span', { className: 'dsh-datefilter-icon' }, '📅'),
|
|
598
|
+
React.createElement('span', { className: 'dsh-datefilter-text' }, label),
|
|
599
|
+
value !== '' && React.createElement('button', {
|
|
600
|
+
type: 'button', className: 'dsh-datefilter-clear', title: '清除', 'aria-label': '清除日期',
|
|
601
|
+
onClick: (e) => { e.stopPropagation(); onChange('') },
|
|
602
|
+
}, '✕'),
|
|
603
|
+
React.createElement('input', {
|
|
604
|
+
ref, type: 'date', className: 'dsh-datefilter-native', value,
|
|
605
|
+
onChange: (e) => onChange(e.target.value),
|
|
606
|
+
tabIndex: -1, 'aria-label': placeholder || '选择日期',
|
|
607
|
+
}),
|
|
608
|
+
)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/** 🔍 放大镜搜索:点击图标弹出搜索框,输入即过滤;✕ 清除并关闭。不自动隐藏(需人工关闭或刷新)。 */
|
|
612
|
+
function SearchPopup({ value, onChange, placeholder }) {
|
|
613
|
+
const [open, setOpen] = React.useState(false)
|
|
614
|
+
const [pos, setPos] = React.useState({ top: 72, left: 8 })
|
|
615
|
+
const ref = React.useRef(null)
|
|
616
|
+
React.useEffect(() => { if (open && ref.current) { try { ref.current.focus() } catch (e) {} } }, [open])
|
|
617
|
+
const toggle = (e) => {
|
|
618
|
+
if (!open && e && e.currentTarget) {
|
|
619
|
+
const r = e.currentTarget.getBoundingClientRect()
|
|
620
|
+
setPos({ top: r.bottom + 8, left: Math.max(8, Math.min(r.left, (window.innerWidth || 0) - 280)) })
|
|
621
|
+
}
|
|
622
|
+
setOpen((o) => !o)
|
|
623
|
+
}
|
|
624
|
+
return React.createElement('div', { className: 'dsh-searchpop' },
|
|
625
|
+
React.createElement('button', {
|
|
626
|
+
type: 'button', className: 'dsh-searchpop-btn',
|
|
627
|
+
onClick: toggle,
|
|
628
|
+
title: '搜索文件名', 'aria-label': '搜索文件名',
|
|
629
|
+
}, '🔍'),
|
|
630
|
+
open && React.createElement('div', { className: 'dsh-searchpop-box', style: { top: pos.top, left: pos.left } },
|
|
631
|
+
React.createElement('input', {
|
|
632
|
+
ref, type: 'text', className: 'dsh-searchpop-input', value,
|
|
633
|
+
onChange: (e) => onChange(e.target.value), placeholder: placeholder || '搜索文件名…', autoFocus: true,
|
|
634
|
+
}),
|
|
635
|
+
value !== '' && React.createElement('button', {
|
|
636
|
+
type: 'button', className: 'dsh-searchpop-clear', title: '清除', 'aria-label': '清除搜索',
|
|
637
|
+
onMouseDown: (e) => { e.preventDefault(); e.stopPropagation() },
|
|
638
|
+
onClick: (e) => { e.preventDefault(); e.stopPropagation(); onChange(''); setOpen(false); },
|
|
639
|
+
}, '✕'),
|
|
640
|
+
),
|
|
641
|
+
)
|
|
642
|
+
}
|
|
643
|
+
|
|
569
644
|
function UploadSettingsSection() {
|
|
570
645
|
const [state, setState] = React.useState({
|
|
571
646
|
loading: true,
|
|
@@ -579,7 +654,8 @@ window.__ModuleLoader__.load({
|
|
|
579
654
|
const [deleting, setDeleting] = React.useState('')
|
|
580
655
|
const [preview, setPreview] = React.useState(null)
|
|
581
656
|
const [previewMaximized, setPreviewMaximized] = React.useState(false)
|
|
582
|
-
const [
|
|
657
|
+
const [dayFilter, setDayFilter] = React.useState('')
|
|
658
|
+
const [search, setSearch] = React.useState('')
|
|
583
659
|
|
|
584
660
|
async function refresh() {
|
|
585
661
|
setState((current) => ({ ...current, loading: true, error: '' }))
|
|
@@ -675,7 +751,7 @@ window.__ModuleLoader__.load({
|
|
|
675
751
|
for (const file of files) {
|
|
676
752
|
let day = ''
|
|
677
753
|
try {
|
|
678
|
-
day =
|
|
754
|
+
day = localDay(file.modifiedAt)
|
|
679
755
|
} catch {
|
|
680
756
|
day = '未知日期'
|
|
681
757
|
}
|
|
@@ -686,7 +762,11 @@ window.__ModuleLoader__.load({
|
|
|
686
762
|
for (const day of days) groups.push({ day, files: byDay.get(day) })
|
|
687
763
|
return groups
|
|
688
764
|
}
|
|
689
|
-
|
|
765
|
+
// 日期筛选 + 文件名搜索:先按所选天/关键词过滤 state.files,再始终按天分组(空=全部)。
|
|
766
|
+
const shownFiles = (dayFilter || search)
|
|
767
|
+
? state.files.filter((f) => (dayFilter ? (() => { try { return localDay(f.modifiedAt) === dayFilter } catch { return false } })() : true) && matchesSearch(f, search))
|
|
768
|
+
: state.files
|
|
769
|
+
const dateGroups = groupFilesByDate(shownFiles)
|
|
690
770
|
|
|
691
771
|
return React.createElement(
|
|
692
772
|
'section',
|
|
@@ -703,16 +783,8 @@ window.__ModuleLoader__.load({
|
|
|
703
783
|
React.createElement(
|
|
704
784
|
'div',
|
|
705
785
|
{ className: 'dsh-upload-head-actions' },
|
|
706
|
-
React.createElement(
|
|
707
|
-
|
|
708
|
-
{
|
|
709
|
-
type: 'button',
|
|
710
|
-
className: 'dsh-upload-refresh',
|
|
711
|
-
disabled: state.loading,
|
|
712
|
-
onClick: () => setGroupByDate((g) => !g),
|
|
713
|
-
},
|
|
714
|
-
groupByDate ? '日期 ✓' : '日期',
|
|
715
|
-
),
|
|
786
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
787
|
+
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
716
788
|
React.createElement(
|
|
717
789
|
'button',
|
|
718
790
|
{ type: 'button', className: 'dsh-upload-refresh', disabled: state.loading, onClick: refresh },
|
|
@@ -736,6 +808,9 @@ window.__ModuleLoader__.load({
|
|
|
736
808
|
!state.loading && state.files.length === 0
|
|
737
809
|
? React.createElement('div', { className: 'dsh-upload-empty' }, '当前没有已上传文件。')
|
|
738
810
|
: null,
|
|
811
|
+
!state.loading && (dayFilter !== '' || search !== '') && shownFiles.length === 0 && state.files.length > 0
|
|
812
|
+
? React.createElement('div', { className: 'dsh-upload-empty' }, '没有匹配的文件。')
|
|
813
|
+
: null,
|
|
739
814
|
preview
|
|
740
815
|
? React.createElement(
|
|
741
816
|
'div',
|
|
@@ -780,7 +855,7 @@ window.__ModuleLoader__.load({
|
|
|
780
855
|
React.createElement(
|
|
781
856
|
'div',
|
|
782
857
|
{ className: 'dsh-upload-list' },
|
|
783
|
-
(dateGroups === null ? [{ day: null, files:
|
|
858
|
+
(dateGroups === null ? [{ day: null, files: shownFiles }] : dateGroups).map((group) => React.createElement(
|
|
784
859
|
'div',
|
|
785
860
|
{ key: group.day ?? '__all__', className: 'dsh-upload-group' },
|
|
786
861
|
group.day !== null
|
|
@@ -841,9 +916,24 @@ window.__ModuleLoader__.load({
|
|
|
841
916
|
.dsh-upload-settings{display:flex;flex-direction:column;gap:16px;min-width:0;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
842
917
|
.dsh-upload-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
|
|
843
918
|
.dsh-upload-head-actions{display:flex;gap:8px;align-items:center;flex:none}
|
|
919
|
+
.dsh-searchpop{position:relative;display:inline-flex;align-items:center;flex:none}
|
|
920
|
+
.dsh-searchpop-btn{width:30px;height:30px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary);font-size:14px;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
|
|
921
|
+
.dsh-searchpop-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
922
|
+
.dsh-searchpop-box{position:fixed;z-index:60;display:flex;align-items:center;gap:6px;background:var(--dsw-specific-input-major,#0f1720);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;padding:6px;box-shadow:var(--dsw-shadow-lv3);max-width:calc(100vw - 16px)}
|
|
923
|
+
.dsh-searchpop-input{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:transparent;color:var(--dsw-alias-label-primary);padding:5px 9px;font:inherit;font-size:12px;line-height:18px;min-width:160px}
|
|
924
|
+
.dsh-searchpop-input::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
925
|
+
.dsh-searchpop-clear{background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1;cursor:pointer;padding:0 3px}
|
|
926
|
+
.dsh-searchpop-clear:hover{color:var(--dsw-alias-label-primary)}
|
|
844
927
|
.dsh-upload-settings h2{margin:0;font-size:20px;line-height:28px}
|
|
845
928
|
.dsh-upload-settings p{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
846
929
|
.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}
|
|
930
|
+
.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}
|
|
931
|
+
.dsh-datefilter:hover{border-color:var(--dsw-alias-state-business-primary)}
|
|
932
|
+
.dsh-datefilter-icon{font-size:13px;flex:none}
|
|
933
|
+
.dsh-datefilter-text{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
934
|
+
.dsh-datefilter-native{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;pointer-events:auto;z-index:1}
|
|
935
|
+
.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}
|
|
936
|
+
.dsh-datefilter-clear:hover{color:var(--dsw-alias-label-primary)}
|
|
847
937
|
.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
938
|
.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
939
|
.dsh-upload-root span{font-size:12px;color:var(--dsw-alias-label-secondary)}
|
|
@@ -901,6 +991,8 @@ window.__ModuleLoader__.load({
|
|
|
901
991
|
const [preview, setPreview] = React.useState(null)
|
|
902
992
|
const [busy, setBusy] = React.useState(false)
|
|
903
993
|
const [collapsed, setCollapsed] = React.useState({})
|
|
994
|
+
const [dayFilter, setDayFilter] = React.useState('')
|
|
995
|
+
const [search, setSearch] = React.useState('')
|
|
904
996
|
const [copied, setCopied] = React.useState(false)
|
|
905
997
|
const [editing, setEditing] = React.useState(false)
|
|
906
998
|
const [edited, setEdited] = React.useState('')
|
|
@@ -1083,14 +1175,29 @@ window.__ModuleLoader__.load({
|
|
|
1083
1175
|
padding: '10px 14px', borderBottom: '1px solid var(--dsw-alias-border-l2)', flex: 'none',
|
|
1084
1176
|
}
|
|
1085
1177
|
|
|
1178
|
+
// 日期筛选:按所选天过滤各组文件,隐藏空组(空日期=全部)。
|
|
1179
|
+
const shownGroups = ((dayFilter || search) && groups !== null)
|
|
1180
|
+
? groups
|
|
1181
|
+
.map((g) => ({ folder: g.folder, files: g.files.filter((f) =>
|
|
1182
|
+
(dayFilter ? (() => { try { return localDay(f.mtime) === dayFilter } catch { return false } })() : true) &&
|
|
1183
|
+
matchesSearch(f, search)
|
|
1184
|
+
) }))
|
|
1185
|
+
.filter((g) => g.files.length > 0)
|
|
1186
|
+
: groups
|
|
1187
|
+
|
|
1086
1188
|
return React.createElement(
|
|
1087
1189
|
'div',
|
|
1088
1190
|
{ style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
1089
1191
|
React.createElement('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-tertiary)' } }, '工作区输出文件(按文件夹分类,预览 / 下载 / 删除)'),
|
|
1192
|
+
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', minWidth: 0 } },
|
|
1193
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
1194
|
+
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
1195
|
+
),
|
|
1090
1196
|
error !== null && React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, error),
|
|
1091
1197
|
groups === null && error === null && React.createElement('div', { style: metaStyle }, '加载中…'),
|
|
1092
1198
|
groups !== null && groups.length === 0 && React.createElement('div', { style: metaStyle }, '目录为空'),
|
|
1093
|
-
groups !== null && groups.
|
|
1199
|
+
(dayFilter || search) && groups !== null && groups.length > 0 && shownGroups !== null && shownGroups.length === 0 && React.createElement('div', { style: metaStyle }, '没有匹配的文件'),
|
|
1200
|
+
shownGroups !== null && shownGroups.map((group) => React.createElement(
|
|
1094
1201
|
'div',
|
|
1095
1202
|
{ key: group.folder, style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
1096
1203
|
React.createElement(
|
|
@@ -1276,11 +1383,40 @@ window.__ModuleLoader__.load({
|
|
|
1276
1383
|
"files.maximize": "Maximize",
|
|
1277
1384
|
"files.restore": "Restore"
|
|
1278
1385
|
};
|
|
1386
|
+
/** 技能文档文件名/路径匹配:空格分词,全部命中才显示。 */
|
|
1387
|
+
const skillMatch = (f, q) => {
|
|
1388
|
+
const s = String(q || "").trim().toLowerCase();
|
|
1389
|
+
if (!s) return true;
|
|
1390
|
+
const hay = ((f.name || "") + " " + (f.path || "")).toLowerCase();
|
|
1391
|
+
return s.split(/\s+/).filter(Boolean).every((tok) => hay.indexOf(tok) !== -1);
|
|
1392
|
+
};
|
|
1393
|
+
/** 🔍 放大镜搜索弹窗(技能文档用):点击弹出搜索框,✕ 清除并关闭。不自动隐藏。 */
|
|
1394
|
+
function SkillSearchPop({ value, onChange, placeholder }) {
|
|
1395
|
+
const [open, setOpen] = react.useState(false);
|
|
1396
|
+
const [pos, setPos] = react.useState({ top: 72, left: 8 });
|
|
1397
|
+
const ref = react.useRef(null);
|
|
1398
|
+
react.useEffect(() => { if (open && ref.current) { try { ref.current.focus(); } catch (e) {} } }, [open]);
|
|
1399
|
+
const toggle = (e) => {
|
|
1400
|
+
if (!open && e && e.currentTarget) {
|
|
1401
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
1402
|
+
setPos({ top: r.bottom + 8, left: Math.max(8, Math.min(r.left, (window.innerWidth || 0) - 280)) });
|
|
1403
|
+
}
|
|
1404
|
+
setOpen((o) => !o);
|
|
1405
|
+
};
|
|
1406
|
+
return react_jsx_runtime.jsxs("div", { className: "dsh-searchpop", children: [
|
|
1407
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dsh-searchpop-btn", onClick: toggle, title: "搜索技能/文件", "aria-label": "搜索技能/文件", children: "🔍" }),
|
|
1408
|
+
open && react_jsx_runtime.jsxs("div", { className: "dsh-searchpop-box", style: { top: pos.top, left: pos.left }, children: [
|
|
1409
|
+
react_jsx_runtime.jsx("input", { ref, type: "text", className: "dsh-searchpop-input", value, onChange: (e) => onChange(e.target.value), placeholder: placeholder || "搜索技能/文件…", autoFocus: true }),
|
|
1410
|
+
value !== "" && react_jsx_runtime.jsx("button", { type: "button", className: "dsh-searchpop-clear", title: "清除", "aria-label": "清除搜索", onMouseDown: (e) => { e.preventDefault(); e.stopPropagation(); }, onClick: (e) => { e.preventDefault(); e.stopPropagation(); onChange(""); setOpen(false); }, children: "✕" })
|
|
1411
|
+
] })
|
|
1412
|
+
] });
|
|
1413
|
+
}
|
|
1279
1414
|
function SkillsSection({ t }) {
|
|
1280
1415
|
const [groups, setGroups] = react.useState(null);
|
|
1281
1416
|
const [error, setError] = react.useState(null);
|
|
1282
1417
|
const [preview, setPreview] = react.useState(null);
|
|
1283
1418
|
const [collapsed, setCollapsed] = react.useState({});
|
|
1419
|
+
const [search, setSearch] = react.useState("");
|
|
1284
1420
|
const [maximized, setMaximized] = react.useState(false);
|
|
1285
1421
|
const [editing, setEditing] = react.useState(false);
|
|
1286
1422
|
const [edited, setEdited] = react.useState("");
|
|
@@ -1406,34 +1542,47 @@ window.__ModuleLoader__.load({
|
|
|
1406
1542
|
padding: "8px 12px", borderBottom: "1px solid var(--dsw-alias-border-l2)", flex: "none", flexWrap: "wrap"
|
|
1407
1543
|
};
|
|
1408
1544
|
|
|
1545
|
+
// 检索:按名称/路径过滤各技能夹的 md 文件,隐藏空夹;检索时自动展开命中夹。
|
|
1546
|
+
const shownGroups = (search && groups !== null)
|
|
1547
|
+
? groups.map((g) => ({ folder: g.folder, files: g.files.filter((f) => skillMatch(f, search)) })).filter((g) => g.files.length > 0)
|
|
1548
|
+
: groups;
|
|
1549
|
+
const searching = !!String(search || "").trim();
|
|
1550
|
+
|
|
1409
1551
|
return react_jsx_runtime.jsxs("div", {
|
|
1410
1552
|
style: { display: "flex", flexDirection: "column", gap: 6, minWidth: 0 },
|
|
1411
1553
|
children: [
|
|
1412
|
-
react_jsx_runtime.jsx("div", { style: {
|
|
1554
|
+
react_jsx_runtime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", minWidth: 0 }, children: [
|
|
1555
|
+
react_jsx_runtime.jsx("span", { style: { fontSize: 13, color: "var(--dsw-alias-label-tertiary)" }, children: t("files.hint") }),
|
|
1556
|
+
react_jsx_runtime.jsx(SkillSearchPop, { value: search, onChange: setSearch, placeholder: "搜索技能/文件…" })
|
|
1557
|
+
] }),
|
|
1413
1558
|
error !== null && react_jsx_runtime.jsx("div", { style: { fontSize: 12, color: "var(--dsw-alias-state-error-primary)" }, children: error }),
|
|
1414
1559
|
groups === null && error === null && react_jsx_runtime.jsx("div", { style: metaStyle, children: t("files.loading") }),
|
|
1415
1560
|
groups !== null && groups.length === 0 && react_jsx_runtime.jsx("div", { style: metaStyle, children: t("files.empty") }),
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1561
|
+
shownGroups !== null && shownGroups.map((group) => {
|
|
1562
|
+
const isCollapsed = searching ? false : !!collapsed[group.folder];
|
|
1563
|
+
return react_jsx_runtime.jsxs("div", {
|
|
1564
|
+
style: { display: "flex", flexDirection: "column", gap: 6, minWidth: 0 },
|
|
1565
|
+
children: [
|
|
1566
|
+
react_jsx_runtime.jsx("button", {
|
|
1567
|
+
type: "button",
|
|
1568
|
+
style: folderBtnStyle,
|
|
1569
|
+
"aria-expanded": !isCollapsed,
|
|
1570
|
+
onClick: () => toggleFolder(group.folder),
|
|
1571
|
+
children: `${isCollapsed ? "▸" : "▾"} ${group.folder} (${group.files.length})`
|
|
1572
|
+
}),
|
|
1573
|
+
!isCollapsed && group.files.map((f) => react_jsx_runtime.jsxs("div", {
|
|
1574
|
+
style: rowStyle,
|
|
1575
|
+
children: [
|
|
1576
|
+
react_jsx_runtime.jsx("span", { style: nameStyle, title: f.path, children: f.path }),
|
|
1577
|
+
react_jsx_runtime.jsx("span", { style: metaStyle, children: `${f.size < 1024 ? `${f.size} B` : `${(f.size / 1024).toFixed(1)} KiB`}` }),
|
|
1578
|
+
react_jsx_runtime.jsx("button", { type: "button", style: btnStyle, onClick: () => openDoc(f.path), children: t("files.preview") }),
|
|
1579
|
+
react_jsx_runtime.jsx("a", { href: "/dsh-skill-docs/skill-doc?path=" + encodeURIComponent(f.path) + "&download=1", download: f.name, style: btnStyle, children: t("files.download") })
|
|
1580
|
+
]
|
|
1581
|
+
}, f.path))
|
|
1582
|
+
]
|
|
1583
|
+
}, group.folder);
|
|
1584
|
+
}),
|
|
1585
|
+
searching && shownGroups !== null && shownGroups.length === 0 && react_jsx_runtime.jsx("div", { style: metaStyle, children: "没有匹配的技能文档" }),
|
|
1437
1586
|
preview !== null && react_jsx_runtime.jsxs("div", {
|
|
1438
1587
|
style: overlayStyle,
|
|
1439
1588
|
onClick: () => setPreview(null),
|
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.7",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -1280,7 +1280,7 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1280
1280
|
const viewHref = previewable
|
|
1281
1281
|
? (isPdf ? `workspace-file?path=${rel}&inline=1` : isDocx ? `docx-preview?path=${rel}` : isPptx ? `pptx-preview?path=${rel}` : `workspace-preview?path=${rel}&from=list`)
|
|
1282
1282
|
: `workspace-file?path=${rel}&download=1`;
|
|
1283
|
-
return `<tr>
|
|
1283
|
+
return `<tr data-ts="${Math.floor(f.mtime)}" data-name="${escapeHtml(f.name)}">
|
|
1284
1284
|
<td class="name"><a class="flink" href="${viewHref}" data-preview="${escapeHtml(viewHref)}" title="${escapeHtml(f.path)}">${escapeHtml(f.name)}</a></td>
|
|
1285
1285
|
<td class="size">${humanSize(f.size)}</td>
|
|
1286
1286
|
<td class="time">${humanTime(f.mtime)}</td>
|
|
@@ -1290,7 +1290,7 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1290
1290
|
</td>
|
|
1291
1291
|
</tr>`;
|
|
1292
1292
|
}).join("");
|
|
1293
|
-
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>
|
|
1294
1294
|
<table><thead><tr><th>文件</th><th>大小</th><th>修改</th><th>操作</th></tr></thead><tbody>${rows}</tbody></table></div>`;
|
|
1295
1295
|
};
|
|
1296
1296
|
return `<!DOCTYPE html>
|
|
@@ -1334,6 +1334,33 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1334
1334
|
.group { margin-bottom:26px; }
|
|
1335
1335
|
.group h2 { font-size:15px; margin:0 0 8px; color:var(--lp-h2); }
|
|
1336
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
|
+
.fsearch-wrap { position:relative; display:inline-flex; align-items:center; flex:none; }
|
|
1353
|
+
.fsearch-btn { width:30px; height:30px; border:1px solid var(--lp-border); border-radius:8px; background:var(--lp-btn-bg); color:var(--lp-fg); font-size:14px; line-height:1; cursor:pointer; display:inline-flex; align-items:center; justify-content:center; }
|
|
1354
|
+
.fsearch-btn:hover { background:var(--lp-btn-hover); }
|
|
1355
|
+
.fsearch-box { position:fixed; z-index:60; display:flex; align-items:center; gap:6px; background:var(--lp-bar-bg); border:1px solid var(--lp-border); border-radius:10px; padding:6px; box-shadow:var(--lp-shadow-lv3, 0 10px 30px rgba(0,0,0,.35)); max-width:calc(100vw - 16px); }
|
|
1356
|
+
.fsearch { background:var(--lp-btn-bg); color:var(--lp-fg); border:1px solid var(--lp-border); border-radius:8px; padding:5px 10px; font-size:13px; min-width:160px; }
|
|
1357
|
+
.fsearch::placeholder { color:var(--lp-meta); }
|
|
1358
|
+
.fsearch:focus { outline:none; border-color:var(--lp-accent); }
|
|
1359
|
+
.fsearch-clear { background:transparent; border:none; color:var(--lp-meta); font-size:13px; line-height:1; cursor:pointer; padding:0 3px; }
|
|
1360
|
+
.fsearch-clear:hover { color:var(--lp-fg); }
|
|
1361
|
+
.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; }
|
|
1362
|
+
.btn2:hover { background:var(--lp-btn-hover); }
|
|
1363
|
+
.daybtn.active { background:#2563eb; color:#fff; }
|
|
1337
1364
|
table { width:100%; border-collapse:collapse; background:var(--lp-table-bg); border:1px solid var(--lp-table-border); border-radius:10px; overflow:hidden; }
|
|
1338
1365
|
th, td { text-align:left; padding:9px 14px; font-size:13px; border-bottom:1px solid var(--lp-cell-border); }
|
|
1339
1366
|
th { background:var(--lp-th-bg); color:var(--lp-meta); font-weight:500; }
|
|
@@ -1372,11 +1399,29 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1372
1399
|
<a class="${all ? "on" : ""}" href="${allHref}">${ICON_FOLDER_OPEN} 总文件</a>
|
|
1373
1400
|
</div>
|
|
1374
1401
|
<span class="meta">${metaText}</span>
|
|
1402
|
+
<span class="daybox">
|
|
1403
|
+
<label class="daylabel" for="dayFilter">日期</label>
|
|
1404
|
+
<span class="dfilter" id="dfilter">
|
|
1405
|
+
<span class="dfilter-icon">📅</span>
|
|
1406
|
+
<span class="dfilter-text" id="dfilterText">选择日期</span>
|
|
1407
|
+
<button type="button" class="dfilter-clear" id="dfilterClear" title="清除" aria-label="清除日期" style="display:none">✕</button>
|
|
1408
|
+
<input type="date" id="dayFilter" class="dfilter-native" aria-label="按日期筛选">
|
|
1409
|
+
</span>
|
|
1410
|
+
<button type="button" class="btn2 daybtn" id="dayToday">今天</button>
|
|
1411
|
+
<span class="fsearch-wrap" id="fsearchWrap">
|
|
1412
|
+
<button type="button" class="fsearch-btn" id="fsearchBtn" title="搜索文件名" aria-label="搜索文件名">🔍</button>
|
|
1413
|
+
<span class="fsearch-box" id="fsearchBox" style="display:none">
|
|
1414
|
+
<input type="text" class="fsearch" id="fsearch" placeholder="搜索文件名…" aria-label="搜索文件名">
|
|
1415
|
+
<button type="button" class="fsearch-clear" id="fsearchClear" title="清除" aria-label="清除搜索">✕</button>
|
|
1416
|
+
</span>
|
|
1417
|
+
</span>
|
|
1418
|
+
</span>
|
|
1375
1419
|
<span class="spacer"></span>
|
|
1376
1420
|
<a class="btn" href="${refreshHref}">⟳ 刷新</a>
|
|
1377
1421
|
</div>
|
|
1378
1422
|
<div class="wrap">
|
|
1379
1423
|
${groups.map(section).join("") || `<p class="empty">${ws && !all ? `工作区「${escapeHtml(ws)}」还没有文件` : "工作区还没有文件"}</p>`}
|
|
1424
|
+
<p class="empty" id="filterEmpty" style="display:none">没有匹配的文件</p>
|
|
1380
1425
|
</div>
|
|
1381
1426
|
<script>
|
|
1382
1427
|
// 预览链接点击 → 通知父窗口(wsOverlay)打开,父窗口用 embed/iframe 渲染,
|
|
@@ -1398,6 +1443,106 @@ document.addEventListener('click', function (e) {
|
|
|
1398
1443
|
} catch (err) { /* 跨域忽略 */ }
|
|
1399
1444
|
location.href = abs;
|
|
1400
1445
|
});
|
|
1446
|
+
// 按文件夹可折叠 + 按日期筛选(前端 JS;mtime 以浏览器本地时区算日期)
|
|
1447
|
+
(function () {
|
|
1448
|
+
var groups = Array.prototype.slice.call(document.querySelectorAll('.group'));
|
|
1449
|
+
// 1) 文件夹头点击 → 折叠/展开
|
|
1450
|
+
groups.forEach(function (g) {
|
|
1451
|
+
var h = g.querySelector('.gtoggle');
|
|
1452
|
+
if (h) h.addEventListener('click', function () { g.classList.toggle('collapsed'); });
|
|
1453
|
+
});
|
|
1454
|
+
// 2) 日期筛选
|
|
1455
|
+
var dayInput = document.getElementById('dayFilter');
|
|
1456
|
+
var dayToday = document.getElementById('dayToday');
|
|
1457
|
+
var dfilter = document.getElementById('dfilter');
|
|
1458
|
+
var dfilterText = document.getElementById('dfilterText');
|
|
1459
|
+
var dfilterClear = document.getElementById('dfilterClear');
|
|
1460
|
+
var fsearch = document.getElementById('fsearch');
|
|
1461
|
+
var filterEmpty = document.getElementById('filterEmpty');
|
|
1462
|
+
// 预览后返回不丢检索:用 sessionStorage 记住检索词与检索框开/关状态。
|
|
1463
|
+
var savedSearch = '';
|
|
1464
|
+
var savedOpen = false;
|
|
1465
|
+
try { savedSearch = sessionStorage.getItem('wsb-search') || ''; } catch (e) {}
|
|
1466
|
+
try { savedOpen = sessionStorage.getItem('wsb-search-open') === '1'; } catch (e) {}
|
|
1467
|
+
if (fsearch) fsearch.value = savedSearch;
|
|
1468
|
+
var metaEl = document.querySelector('.bar .meta');
|
|
1469
|
+
var origMeta = metaEl ? metaEl.textContent : '';
|
|
1470
|
+
function pad(v) { return v < 10 ? '0' + v : v; }
|
|
1471
|
+
function fmtDate(ts) {
|
|
1472
|
+
var d = new Date(ts);
|
|
1473
|
+
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
|
1474
|
+
}
|
|
1475
|
+
function matchName(tr) {
|
|
1476
|
+
var q = (fsearch && fsearch.value || '').trim().toLowerCase();
|
|
1477
|
+
if (!q) return true;
|
|
1478
|
+
var hay = String(tr.dataset.name || '').toLowerCase();
|
|
1479
|
+
return q.split(/\s+/).filter(Boolean).every(function (tok) { return hay.indexOf(tok) !== -1; });
|
|
1480
|
+
}
|
|
1481
|
+
// 预计算每行本地日期
|
|
1482
|
+
Array.prototype.forEach.call(document.querySelectorAll('tr[data-ts]'), function (tr) {
|
|
1483
|
+
tr.dataset.date = fmtDate(Number(tr.dataset.ts));
|
|
1484
|
+
});
|
|
1485
|
+
var todayStr = fmtDate(Date.now());
|
|
1486
|
+
function apply() {
|
|
1487
|
+
var sel = dayInput.value;
|
|
1488
|
+
var visTotal = 0;
|
|
1489
|
+
groups.forEach(function (g) {
|
|
1490
|
+
var vis = 0;
|
|
1491
|
+
Array.prototype.forEach.call(g.querySelectorAll('tr[data-date]'), function (tr) {
|
|
1492
|
+
var ok = (!sel || tr.dataset.date === sel) && matchName(tr);
|
|
1493
|
+
tr.style.display = ok ? '' : 'none';
|
|
1494
|
+
if (ok) vis++;
|
|
1495
|
+
});
|
|
1496
|
+
var cnt = g.querySelector('.cnt');
|
|
1497
|
+
if (cnt) cnt.textContent = vis;
|
|
1498
|
+
if (vis === 0) g.classList.add('filtered-empty'); else g.classList.remove('filtered-empty');
|
|
1499
|
+
visTotal += vis;
|
|
1500
|
+
});
|
|
1501
|
+
var searching = fsearch && fsearch.value.trim();
|
|
1502
|
+
if (metaEl) metaEl.textContent = (sel || searching) ? ('筛选 ' + (sel || '日期') + (searching ? ' · ' + searching : '') + ' · ' + visTotal + ' 个文件') : origMeta;
|
|
1503
|
+
if (dfilterText) dfilterText.textContent = sel || '选择日期';
|
|
1504
|
+
if (dfilterClear) dfilterClear.style.display = sel ? 'inline-block' : 'none';
|
|
1505
|
+
if (dfilter) dfilter.classList.toggle('has-value', !!sel);
|
|
1506
|
+
if (dayToday) dayToday.classList.toggle('active', sel === todayStr);
|
|
1507
|
+
if (filterEmpty) filterEmpty.style.display = ((sel || searching) && visTotal === 0) ? 'block' : 'none';
|
|
1508
|
+
}
|
|
1509
|
+
// 打开日期选择器:整个控件点击 → showPicker(兜底 focus);点 ✕ 清除(阻止冒泡)。
|
|
1510
|
+
if (dfilter) dfilter.addEventListener('click', function () {
|
|
1511
|
+
var el = dayInput;
|
|
1512
|
+
if (!el) return;
|
|
1513
|
+
if (typeof el.showPicker === 'function') { try { el.showPicker(); return; } catch (e) {} }
|
|
1514
|
+
try { el.focus(); } catch (e) {}
|
|
1515
|
+
});
|
|
1516
|
+
if (dayInput) dayInput.addEventListener('input', apply);
|
|
1517
|
+
if (dayInput) dayInput.addEventListener('change', apply);
|
|
1518
|
+
if (fsearch) fsearch.addEventListener('input', function () { try { sessionStorage.setItem('wsb-search', fsearch.value); } catch (e) {} apply(); });
|
|
1519
|
+
// 🔍 搜索弹窗:点击图标开/关,点✕清除,点击外部关闭。
|
|
1520
|
+
var fsearchBtn = document.getElementById('fsearchBtn');
|
|
1521
|
+
var fsearchBox = document.getElementById('fsearchBox');
|
|
1522
|
+
var fsearchClear = document.getElementById('fsearchClear');
|
|
1523
|
+
var fsearchWrap = document.getElementById('fsearchWrap');
|
|
1524
|
+
var fsearchOpen = false;
|
|
1525
|
+
function setFsearchOpen(open) {
|
|
1526
|
+
fsearchOpen = open;
|
|
1527
|
+
try { sessionStorage.setItem('wsb-search-open', open ? '1' : '0'); } catch (e) {}
|
|
1528
|
+
if (fsearchBox) {
|
|
1529
|
+
fsearchBox.style.display = open ? 'flex' : 'none';
|
|
1530
|
+
if (open && fsearchBtn) {
|
|
1531
|
+
var r = fsearchBtn.getBoundingClientRect();
|
|
1532
|
+
fsearchBox.style.top = (r.bottom + 8) + 'px';
|
|
1533
|
+
fsearchBox.style.left = Math.max(8, Math.min(r.left, (window.innerWidth || 0) - 280)) + 'px';
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (open && fsearch) { try { fsearch.focus(); } catch (e) {} }
|
|
1537
|
+
}
|
|
1538
|
+
if (fsearchBtn) fsearchBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); setFsearchOpen(!fsearchOpen); });
|
|
1539
|
+
if (fsearchClear) fsearchClear.addEventListener('mousedown', function (e) { e.preventDefault(); e.stopPropagation(); });
|
|
1540
|
+
if (fsearchClear) fsearchClear.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); fsearch.value = ''; try { sessionStorage.removeItem('wsb-search'); sessionStorage.removeItem('wsb-search-open'); } catch (e2) {} apply(); setFsearchOpen(false); });
|
|
1541
|
+
if (dayToday) dayToday.addEventListener('click', function () { dayInput.value = todayStr; apply(); });
|
|
1542
|
+
if (dfilterClear) dfilterClear.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); dayInput.value = ''; apply(); });
|
|
1543
|
+
if (savedOpen) setFsearchOpen(true);
|
|
1544
|
+
apply();
|
|
1545
|
+
})();
|
|
1401
1546
|
</script>
|
|
1402
1547
|
</body>
|
|
1403
1548
|
</html>`;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.7",
|
|
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",
|