dsh-long-plugins 1.3.6 → 1.3.8
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 +4 -3
- package/client/client.js +181 -33
- package/dsh.plugin.json +1 -1
- package/lib/index.js +143 -11
- package/package.json +1 -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 自动刷新) |
|
|
@@ -73,6 +73,7 @@ Add to the profile's `cordis.patch.yml`:
|
|
|
73
73
|
- `trustedHosts` — required for the browser trust fence (same as the built-in Web API).
|
|
74
74
|
- `skillsRoot` — root directory browsed by the「技能文档」section. Defaults to `<DSH_HOME>/skills`.
|
|
75
75
|
- `md2docxScript` — (optional) override the Markdown→Word script path. Defaults to the bundled `lib/md2docx.py`.
|
|
76
|
+
- `excludedWorkspaceNames` — (optional) extra file names to hide from the workspace file browse / 输出文件 (deployment & plugin config clutter). The plugin already hides a default blocklist (`index.html`, `serve.log`, `docker-compose.*`, `package*.json`, `*.log`, `*.lock`, `Dockerfile`, …); add your own here, e.g. `excludedWorkspaceNames: ['jackylong1987__dsh-long-plugins.yml']`. This only hides the listed names — AI-generated `.yml`/`.html`/`.json` reports still show.
|
|
76
77
|
|
|
77
78
|
## md2docx 工具(Markdown → Word)
|
|
78
79
|
|
package/client/client.js
CHANGED
|
@@ -81,6 +81,20 @@ window.__ModuleLoader__.load({
|
|
|
81
81
|
return base.replace(/\.[^.]+$/, '')
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/** 轻量 toast:短暂显示一条消息(用于「粘贴的文件格式不支持」等提示)。 */
|
|
85
|
+
function showToast(text, kind) {
|
|
86
|
+
let t = document.querySelector('.dsh-long-toast')
|
|
87
|
+
if (!t) {
|
|
88
|
+
t = document.createElement('div')
|
|
89
|
+
t.className = 'dsh-long-toast'
|
|
90
|
+
document.body.appendChild(t)
|
|
91
|
+
}
|
|
92
|
+
t.textContent = text
|
|
93
|
+
t.className = 'dsh-long-toast ' + (kind || '') + ' show'
|
|
94
|
+
clearTimeout(t._timer)
|
|
95
|
+
t._timer = setTimeout(() => { t.className = 'dsh-long-toast' }, 2800)
|
|
96
|
+
}
|
|
97
|
+
|
|
84
98
|
/** Trigger a browser download for a URL (no navigation, keeps the page). */
|
|
85
99
|
function triggerDownload(url, name) {
|
|
86
100
|
const a = document.createElement('a')
|
|
@@ -469,15 +483,48 @@ window.__ModuleLoader__.load({
|
|
|
469
483
|
}
|
|
470
484
|
}
|
|
471
485
|
}
|
|
486
|
+
// 粘贴非图片文件 → 走与回形针/拖放相同的上传管线,并阻止 DSH 核心的「仅支持图片」门;
|
|
487
|
+
// 纯文本/图片粘贴放行给 DSH 原生;上传失败(不支持的格式等)给可见提示。
|
|
488
|
+
const onPaste = async (e) => {
|
|
489
|
+
const cd = e.clipboardData
|
|
490
|
+
if (!cd) return
|
|
491
|
+
const files = []
|
|
492
|
+
const items = cd.items
|
|
493
|
+
if (items) {
|
|
494
|
+
for (const item of items) {
|
|
495
|
+
if (item.kind === 'file' && typeof item.getAsFile === 'function') {
|
|
496
|
+
const f = item.getAsFile()
|
|
497
|
+
// 只接管非图片文件;图片交给 DSH 原生(内联显示),避免改变已有行为
|
|
498
|
+
if (f && f.size > 0 && !(f.type || '').startsWith('image/')) files.push(f)
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (files.length === 0) return // 无「非图片文件」→ 放行给 DSH
|
|
503
|
+
// 有文件粘贴:接管整段粘贴,阻止 DSH 后续处理器报「仅支持图片」
|
|
504
|
+
e.preventDefault()
|
|
505
|
+
e.stopPropagation()
|
|
506
|
+
e.stopImmediatePropagation()
|
|
507
|
+
for (const file of files) {
|
|
508
|
+
try {
|
|
509
|
+
const stored = await uploadFile(file)
|
|
510
|
+
controller.attach(sessionId, stored)
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.warn(`[dsh-long-plugins] 粘贴上传失败:${file.name} → ${errorMessage(error)}`)
|
|
513
|
+
showToast(`不支持该文件格式:${file.name}`, 'error')
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
472
517
|
window.addEventListener('dragenter', onDragEnter, true)
|
|
473
518
|
window.addEventListener('dragover', onDragOver, true)
|
|
474
519
|
window.addEventListener('dragleave', onDragLeave, true)
|
|
475
520
|
window.addEventListener('drop', onDrop, true)
|
|
521
|
+
window.addEventListener('paste', onPaste, true)
|
|
476
522
|
return () => {
|
|
477
523
|
window.removeEventListener('dragenter', onDragEnter, true)
|
|
478
524
|
window.removeEventListener('dragover', onDragOver, true)
|
|
479
525
|
window.removeEventListener('dragleave', onDragLeave, true)
|
|
480
526
|
window.removeEventListener('drop', onDrop, true)
|
|
527
|
+
window.removeEventListener('paste', onPaste, true)
|
|
481
528
|
}
|
|
482
529
|
}, [controller, sessionId])
|
|
483
530
|
|
|
@@ -573,6 +620,14 @@ window.__ModuleLoader__.load({
|
|
|
573
620
|
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate())
|
|
574
621
|
}
|
|
575
622
|
|
|
623
|
+
/** 文件名搜索过滤:空格分隔的每个词都需命中(不区分大小写,匹配文件名+路径)。 */
|
|
624
|
+
const matchesSearch = (file, search) => {
|
|
625
|
+
const q = String(search || '').trim().toLowerCase()
|
|
626
|
+
if (!q) return true
|
|
627
|
+
const hay = ((file.name || '') + ' ' + (file.path || '')).toLowerCase()
|
|
628
|
+
return q.split(/\s+/).filter(Boolean).every((tok) => hay.indexOf(tok) !== -1)
|
|
629
|
+
}
|
|
630
|
+
|
|
576
631
|
/** 自定义日期筛选控件:显示 📅 + 文字,点击/触摸打开系统日期选择器,自带 ✕ 清除。
|
|
577
632
|
* 原生 input[type=date] 在手机端是空框、无图标;此处整个控件包一层 onClick,
|
|
578
633
|
* 统一调用 input.showPicker(),故点图标/文字/任意位置都触发(桌面/手机一致)。 */
|
|
@@ -600,6 +655,39 @@ window.__ModuleLoader__.load({
|
|
|
600
655
|
)
|
|
601
656
|
}
|
|
602
657
|
|
|
658
|
+
/** 🔍 放大镜搜索:点击图标弹出搜索框,输入即过滤;✕ 清除并关闭。不自动隐藏(需人工关闭或刷新)。 */
|
|
659
|
+
function SearchPopup({ value, onChange, placeholder }) {
|
|
660
|
+
const [open, setOpen] = React.useState(false)
|
|
661
|
+
const [pos, setPos] = React.useState({ top: 72, left: 8 })
|
|
662
|
+
const ref = React.useRef(null)
|
|
663
|
+
React.useEffect(() => { if (open && ref.current) { try { ref.current.focus() } catch (e) {} } }, [open])
|
|
664
|
+
const toggle = (e) => {
|
|
665
|
+
if (!open && e && e.currentTarget) {
|
|
666
|
+
const r = e.currentTarget.getBoundingClientRect()
|
|
667
|
+
setPos({ top: r.bottom + 8, left: Math.max(8, Math.min(r.left, (window.innerWidth || 0) - 280)) })
|
|
668
|
+
}
|
|
669
|
+
setOpen((o) => !o)
|
|
670
|
+
}
|
|
671
|
+
return React.createElement('div', { className: 'dsh-searchpop' },
|
|
672
|
+
React.createElement('button', {
|
|
673
|
+
type: 'button', className: 'dsh-searchpop-btn',
|
|
674
|
+
onClick: toggle,
|
|
675
|
+
title: '搜索文件名', 'aria-label': '搜索文件名',
|
|
676
|
+
}, '🔍'),
|
|
677
|
+
open && React.createElement('div', { className: 'dsh-searchpop-box', style: { top: pos.top, left: pos.left } },
|
|
678
|
+
React.createElement('input', {
|
|
679
|
+
ref, type: 'text', className: 'dsh-searchpop-input', value,
|
|
680
|
+
onChange: (e) => onChange(e.target.value), placeholder: placeholder || '搜索文件名…', autoFocus: true,
|
|
681
|
+
}),
|
|
682
|
+
value !== '' && React.createElement('button', {
|
|
683
|
+
type: 'button', className: 'dsh-searchpop-clear', title: '清除', 'aria-label': '清除搜索',
|
|
684
|
+
onMouseDown: (e) => { e.preventDefault(); e.stopPropagation() },
|
|
685
|
+
onClick: (e) => { e.preventDefault(); e.stopPropagation(); onChange(''); setOpen(false); },
|
|
686
|
+
}, '✕'),
|
|
687
|
+
),
|
|
688
|
+
)
|
|
689
|
+
}
|
|
690
|
+
|
|
603
691
|
function UploadSettingsSection() {
|
|
604
692
|
const [state, setState] = React.useState({
|
|
605
693
|
loading: true,
|
|
@@ -614,6 +702,7 @@ window.__ModuleLoader__.load({
|
|
|
614
702
|
const [preview, setPreview] = React.useState(null)
|
|
615
703
|
const [previewMaximized, setPreviewMaximized] = React.useState(false)
|
|
616
704
|
const [dayFilter, setDayFilter] = React.useState('')
|
|
705
|
+
const [search, setSearch] = React.useState('')
|
|
617
706
|
|
|
618
707
|
async function refresh() {
|
|
619
708
|
setState((current) => ({ ...current, loading: true, error: '' }))
|
|
@@ -720,9 +809,9 @@ window.__ModuleLoader__.load({
|
|
|
720
809
|
for (const day of days) groups.push({ day, files: byDay.get(day) })
|
|
721
810
|
return groups
|
|
722
811
|
}
|
|
723
|
-
//
|
|
724
|
-
const shownFiles = dayFilter
|
|
725
|
-
? state.files.filter((f) => { try { return localDay(f.modifiedAt) === dayFilter } catch { return false } })
|
|
812
|
+
// 日期筛选 + 文件名搜索:先按所选天/关键词过滤 state.files,再始终按天分组(空=全部)。
|
|
813
|
+
const shownFiles = (dayFilter || search)
|
|
814
|
+
? state.files.filter((f) => (dayFilter ? (() => { try { return localDay(f.modifiedAt) === dayFilter } catch { return false } })() : true) && matchesSearch(f, search))
|
|
726
815
|
: state.files
|
|
727
816
|
const dateGroups = groupFilesByDate(shownFiles)
|
|
728
817
|
|
|
@@ -741,6 +830,7 @@ window.__ModuleLoader__.load({
|
|
|
741
830
|
React.createElement(
|
|
742
831
|
'div',
|
|
743
832
|
{ className: 'dsh-upload-head-actions' },
|
|
833
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
744
834
|
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
745
835
|
React.createElement(
|
|
746
836
|
'button',
|
|
@@ -765,8 +855,8 @@ window.__ModuleLoader__.load({
|
|
|
765
855
|
!state.loading && state.files.length === 0
|
|
766
856
|
? React.createElement('div', { className: 'dsh-upload-empty' }, '当前没有已上传文件。')
|
|
767
857
|
: null,
|
|
768
|
-
!state.loading && dayFilter !== '' && shownFiles.length === 0 && state.files.length > 0
|
|
769
|
-
? React.createElement('div', { className: 'dsh-upload-empty' }, '
|
|
858
|
+
!state.loading && (dayFilter !== '' || search !== '') && shownFiles.length === 0 && state.files.length > 0
|
|
859
|
+
? React.createElement('div', { className: 'dsh-upload-empty' }, '没有匹配的文件。')
|
|
770
860
|
: null,
|
|
771
861
|
preview
|
|
772
862
|
? React.createElement(
|
|
@@ -873,6 +963,14 @@ window.__ModuleLoader__.load({
|
|
|
873
963
|
.dsh-upload-settings{display:flex;flex-direction:column;gap:16px;min-width:0;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
874
964
|
.dsh-upload-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
|
|
875
965
|
.dsh-upload-head-actions{display:flex;gap:8px;align-items:center;flex:none}
|
|
966
|
+
.dsh-searchpop{position:relative;display:inline-flex;align-items:center;flex:none}
|
|
967
|
+
.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}
|
|
968
|
+
.dsh-searchpop-btn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
969
|
+
.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)}
|
|
970
|
+
.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}
|
|
971
|
+
.dsh-searchpop-input::placeholder{color:var(--dsw-alias-label-tertiary)}
|
|
972
|
+
.dsh-searchpop-clear{background:transparent;border:none;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:1;cursor:pointer;padding:0 3px}
|
|
973
|
+
.dsh-searchpop-clear:hover{color:var(--dsw-alias-label-primary)}
|
|
876
974
|
.dsh-upload-settings h2{margin:0;font-size:20px;line-height:28px}
|
|
877
975
|
.dsh-upload-settings p{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
878
976
|
.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}
|
|
@@ -897,7 +995,7 @@ window.__ModuleLoader__.load({
|
|
|
897
995
|
.dsh-upload-group-day span{font-weight:400;color:var(--dsw-alias-label-tertiary)}
|
|
898
996
|
.dsh-upload-row{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:6px 8px;border-bottom:1px solid var(--dsw-alias-border-l2);border-radius:8px;font-size:13px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform,transparent);margin-bottom:4px}
|
|
899
997
|
.dsh-upload-file-name{flex:1;min-width:0;display:flex;align-items:center;gap:6px;color:var(--dsw-alias-label-primary)}
|
|
900
|
-
.dsh-upload-file-label{min-width:0;
|
|
998
|
+
.dsh-upload-file-label{min-width:0;white-space:normal;word-break:break-all;overflow-wrap:anywhere}
|
|
901
999
|
.dsh-upload-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}
|
|
902
1000
|
.dsh-upload-file-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
903
1001
|
.dsh-upload-actions{display:flex;flex:none;gap:7px}
|
|
@@ -930,6 +1028,9 @@ window.__ModuleLoader__.load({
|
|
|
930
1028
|
.dsh-upload-preview-open:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
931
1029
|
.dsh-upload-preview-img{max-width:100%;max-height:70vh;object-fit:contain;display:block}
|
|
932
1030
|
.dsh-upload-preview-loading{display:flex;align-items:center;justify-content:center;flex:1;min-height:120px;color:var(--dsw-alias-label-secondary);font-size:13px}
|
|
1031
|
+
.dsh-long-toast{position:fixed;left:50%;bottom:36px;transform:translateX(-50%) translateY(12px);z-index:2000;max-width:min(90vw,520px);padding:10px 16px;border-radius:10px;background:var(--dsw-specific-input-major,#0f1720);border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);font-size:13px;line-height:20px;box-shadow:var(--dsw-shadow-lv3);opacity:0;pointer-events:none;transition:opacity .2s ease,transform .2s ease}
|
|
1032
|
+
.dsh-long-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
|
|
1033
|
+
.dsh-long-toast.error{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary)}
|
|
933
1034
|
`
|
|
934
1035
|
|
|
935
1036
|
const inject = ['slots', 'sessions', 'inputTriggers', 'conversation', 'timer']
|
|
@@ -941,6 +1042,7 @@ window.__ModuleLoader__.load({
|
|
|
941
1042
|
const [busy, setBusy] = React.useState(false)
|
|
942
1043
|
const [collapsed, setCollapsed] = React.useState({})
|
|
943
1044
|
const [dayFilter, setDayFilter] = React.useState('')
|
|
1045
|
+
const [search, setSearch] = React.useState('')
|
|
944
1046
|
const [copied, setCopied] = React.useState(false)
|
|
945
1047
|
const [editing, setEditing] = React.useState(false)
|
|
946
1048
|
const [edited, setEdited] = React.useState('')
|
|
@@ -1087,7 +1189,7 @@ window.__ModuleLoader__.load({
|
|
|
1087
1189
|
borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2)',
|
|
1088
1190
|
background: 'var(--dsw-alias-bg-module-platform, transparent)', fontSize: 13,
|
|
1089
1191
|
}
|
|
1090
|
-
const nameStyle = { flex: 1, minWidth: 0,
|
|
1192
|
+
const nameStyle = { flex: 1, minWidth: 0, whiteSpace: 'normal', wordBreak: 'break-all', overflowWrap: 'anywhere', color: 'var(--dsw-alias-label-primary)' }
|
|
1091
1193
|
const metaStyle = { flex: 'none', color: 'var(--dsw-alias-label-tertiary)', fontSize: 12 }
|
|
1092
1194
|
const btnStyle = { flex: 'none', cursor: 'pointer', border: 'none', borderRadius: 6, padding: '3px 8px', fontSize: 12, background: 'var(--dsw-alias-interactive-bg-hover)', color: 'var(--dsw-alias-label-secondary)' }
|
|
1093
1195
|
const delStyle = { ...btnStyle, color: 'var(--dsw-alias-state-error-primary)' }
|
|
@@ -1124,9 +1226,12 @@ window.__ModuleLoader__.load({
|
|
|
1124
1226
|
}
|
|
1125
1227
|
|
|
1126
1228
|
// 日期筛选:按所选天过滤各组文件,隐藏空组(空日期=全部)。
|
|
1127
|
-
const shownGroups = (dayFilter && groups !== null)
|
|
1229
|
+
const shownGroups = ((dayFilter || search) && groups !== null)
|
|
1128
1230
|
? groups
|
|
1129
|
-
.map((g) => ({ folder: g.folder, files: g.files.filter((f) =>
|
|
1231
|
+
.map((g) => ({ folder: g.folder, files: g.files.filter((f) =>
|
|
1232
|
+
(dayFilter ? (() => { try { return localDay(f.mtime) === dayFilter } catch { return false } })() : true) &&
|
|
1233
|
+
matchesSearch(f, search)
|
|
1234
|
+
) }))
|
|
1130
1235
|
.filter((g) => g.files.length > 0)
|
|
1131
1236
|
: groups
|
|
1132
1237
|
|
|
@@ -1135,12 +1240,13 @@ window.__ModuleLoader__.load({
|
|
|
1135
1240
|
{ style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
1136
1241
|
React.createElement('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-tertiary)' } }, '工作区输出文件(按文件夹分类,预览 / 下载 / 删除)'),
|
|
1137
1242
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', minWidth: 0 } },
|
|
1243
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
1138
1244
|
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
1139
1245
|
),
|
|
1140
1246
|
error !== null && React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, error),
|
|
1141
1247
|
groups === null && error === null && React.createElement('div', { style: metaStyle }, '加载中…'),
|
|
1142
1248
|
groups !== null && groups.length === 0 && React.createElement('div', { style: metaStyle }, '目录为空'),
|
|
1143
|
-
dayFilter && groups !== null && groups.length > 0 && shownGroups !== null && shownGroups.length === 0 && React.createElement('div', { style: metaStyle }, '
|
|
1249
|
+
(dayFilter || search) && groups !== null && groups.length > 0 && shownGroups !== null && shownGroups.length === 0 && React.createElement('div', { style: metaStyle }, '没有匹配的文件'),
|
|
1144
1250
|
shownGroups !== null && shownGroups.map((group) => React.createElement(
|
|
1145
1251
|
'div',
|
|
1146
1252
|
{ key: group.folder, style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
@@ -1161,7 +1267,7 @@ window.__ModuleLoader__.load({
|
|
|
1161
1267
|
React.createElement(
|
|
1162
1268
|
'span',
|
|
1163
1269
|
{ className: 'dsh-ws-name', style: { ...nameStyle, display: 'flex', alignItems: 'center', gap: 6 }, title: f.path },
|
|
1164
|
-
React.createElement('span', { className: 'dsh-ws-file-label', style: { flex: 1, minWidth: 0,
|
|
1270
|
+
React.createElement('span', { className: 'dsh-ws-file-label', style: { flex: 1, minWidth: 0, whiteSpace: 'normal', wordBreak: 'break-all', overflowWrap: 'anywhere' } }, basenameWithoutExt(f.path)),
|
|
1165
1271
|
React.createElement('span', { className: 'dsh-ws-file-type', style: { flex: 'none' } }, fileTypeLabel(f.path)),
|
|
1166
1272
|
),
|
|
1167
1273
|
React.createElement('span', { className: 'dsh-ws-meta', style: metaStyle }, `${sizeText(f.size)} · ${dateText(f.mtime)}`),
|
|
@@ -1327,11 +1433,40 @@ window.__ModuleLoader__.load({
|
|
|
1327
1433
|
"files.maximize": "Maximize",
|
|
1328
1434
|
"files.restore": "Restore"
|
|
1329
1435
|
};
|
|
1436
|
+
/** 技能文档文件名/路径匹配:空格分词,全部命中才显示。 */
|
|
1437
|
+
const skillMatch = (f, q) => {
|
|
1438
|
+
const s = String(q || "").trim().toLowerCase();
|
|
1439
|
+
if (!s) return true;
|
|
1440
|
+
const hay = ((f.name || "") + " " + (f.path || "")).toLowerCase();
|
|
1441
|
+
return s.split(/\s+/).filter(Boolean).every((tok) => hay.indexOf(tok) !== -1);
|
|
1442
|
+
};
|
|
1443
|
+
/** 🔍 放大镜搜索弹窗(技能文档用):点击弹出搜索框,✕ 清除并关闭。不自动隐藏。 */
|
|
1444
|
+
function SkillSearchPop({ value, onChange, placeholder }) {
|
|
1445
|
+
const [open, setOpen] = react.useState(false);
|
|
1446
|
+
const [pos, setPos] = react.useState({ top: 72, left: 8 });
|
|
1447
|
+
const ref = react.useRef(null);
|
|
1448
|
+
react.useEffect(() => { if (open && ref.current) { try { ref.current.focus(); } catch (e) {} } }, [open]);
|
|
1449
|
+
const toggle = (e) => {
|
|
1450
|
+
if (!open && e && e.currentTarget) {
|
|
1451
|
+
const r = e.currentTarget.getBoundingClientRect();
|
|
1452
|
+
setPos({ top: r.bottom + 8, left: Math.max(8, Math.min(r.left, (window.innerWidth || 0) - 280)) });
|
|
1453
|
+
}
|
|
1454
|
+
setOpen((o) => !o);
|
|
1455
|
+
};
|
|
1456
|
+
return react_jsx_runtime.jsxs("div", { className: "dsh-searchpop", children: [
|
|
1457
|
+
react_jsx_runtime.jsx("button", { type: "button", className: "dsh-searchpop-btn", onClick: toggle, title: "搜索技能/文件", "aria-label": "搜索技能/文件", children: "🔍" }),
|
|
1458
|
+
open && react_jsx_runtime.jsxs("div", { className: "dsh-searchpop-box", style: { top: pos.top, left: pos.left }, children: [
|
|
1459
|
+
react_jsx_runtime.jsx("input", { ref, type: "text", className: "dsh-searchpop-input", value, onChange: (e) => onChange(e.target.value), placeholder: placeholder || "搜索技能/文件…", autoFocus: true }),
|
|
1460
|
+
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: "✕" })
|
|
1461
|
+
] })
|
|
1462
|
+
] });
|
|
1463
|
+
}
|
|
1330
1464
|
function SkillsSection({ t }) {
|
|
1331
1465
|
const [groups, setGroups] = react.useState(null);
|
|
1332
1466
|
const [error, setError] = react.useState(null);
|
|
1333
1467
|
const [preview, setPreview] = react.useState(null);
|
|
1334
1468
|
const [collapsed, setCollapsed] = react.useState({});
|
|
1469
|
+
const [search, setSearch] = react.useState("");
|
|
1335
1470
|
const [maximized, setMaximized] = react.useState(false);
|
|
1336
1471
|
const [editing, setEditing] = react.useState(false);
|
|
1337
1472
|
const [edited, setEdited] = react.useState("");
|
|
@@ -1457,34 +1592,47 @@ window.__ModuleLoader__.load({
|
|
|
1457
1592
|
padding: "8px 12px", borderBottom: "1px solid var(--dsw-alias-border-l2)", flex: "none", flexWrap: "wrap"
|
|
1458
1593
|
};
|
|
1459
1594
|
|
|
1595
|
+
// 检索:按名称/路径过滤各技能夹的 md 文件,隐藏空夹;检索时自动展开命中夹。
|
|
1596
|
+
const shownGroups = (search && groups !== null)
|
|
1597
|
+
? groups.map((g) => ({ folder: g.folder, files: g.files.filter((f) => skillMatch(f, search)) })).filter((g) => g.files.length > 0)
|
|
1598
|
+
: groups;
|
|
1599
|
+
const searching = !!String(search || "").trim();
|
|
1600
|
+
|
|
1460
1601
|
return react_jsx_runtime.jsxs("div", {
|
|
1461
1602
|
style: { display: "flex", flexDirection: "column", gap: 6, minWidth: 0 },
|
|
1462
1603
|
children: [
|
|
1463
|
-
react_jsx_runtime.jsx("div", { style: {
|
|
1604
|
+
react_jsx_runtime.jsx("div", { style: { display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap", minWidth: 0 }, children: [
|
|
1605
|
+
react_jsx_runtime.jsx("span", { style: { fontSize: 13, color: "var(--dsw-alias-label-tertiary)" }, children: t("files.hint") }),
|
|
1606
|
+
react_jsx_runtime.jsx(SkillSearchPop, { value: search, onChange: setSearch, placeholder: "搜索技能/文件…" })
|
|
1607
|
+
] }),
|
|
1464
1608
|
error !== null && react_jsx_runtime.jsx("div", { style: { fontSize: 12, color: "var(--dsw-alias-state-error-primary)" }, children: error }),
|
|
1465
1609
|
groups === null && error === null && react_jsx_runtime.jsx("div", { style: metaStyle, children: t("files.loading") }),
|
|
1466
1610
|
groups !== null && groups.length === 0 && react_jsx_runtime.jsx("div", { style: metaStyle, children: t("files.empty") }),
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1611
|
+
shownGroups !== null && shownGroups.map((group) => {
|
|
1612
|
+
const isCollapsed = searching ? false : !!collapsed[group.folder];
|
|
1613
|
+
return react_jsx_runtime.jsxs("div", {
|
|
1614
|
+
style: { display: "flex", flexDirection: "column", gap: 6, minWidth: 0 },
|
|
1615
|
+
children: [
|
|
1616
|
+
react_jsx_runtime.jsx("button", {
|
|
1617
|
+
type: "button",
|
|
1618
|
+
style: folderBtnStyle,
|
|
1619
|
+
"aria-expanded": !isCollapsed,
|
|
1620
|
+
onClick: () => toggleFolder(group.folder),
|
|
1621
|
+
children: `${isCollapsed ? "▸" : "▾"} ${group.folder} (${group.files.length})`
|
|
1622
|
+
}),
|
|
1623
|
+
!isCollapsed && group.files.map((f) => react_jsx_runtime.jsxs("div", {
|
|
1624
|
+
style: rowStyle,
|
|
1625
|
+
children: [
|
|
1626
|
+
react_jsx_runtime.jsx("span", { style: nameStyle, title: f.path, children: f.path }),
|
|
1627
|
+
react_jsx_runtime.jsx("span", { style: metaStyle, children: `${f.size < 1024 ? `${f.size} B` : `${(f.size / 1024).toFixed(1)} KiB`}` }),
|
|
1628
|
+
react_jsx_runtime.jsx("button", { type: "button", style: btnStyle, onClick: () => openDoc(f.path), children: t("files.preview") }),
|
|
1629
|
+
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") })
|
|
1630
|
+
]
|
|
1631
|
+
}, f.path))
|
|
1632
|
+
]
|
|
1633
|
+
}, group.folder);
|
|
1634
|
+
}),
|
|
1635
|
+
searching && shownGroups !== null && shownGroups.length === 0 && react_jsx_runtime.jsx("div", { style: metaStyle, children: "没有匹配的技能文档" }),
|
|
1488
1636
|
preview !== null && react_jsx_runtime.jsxs("div", {
|
|
1489
1637
|
style: overlayStyle,
|
|
1490
1638
|
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.8",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -348,8 +348,27 @@ const DOCUMENT_EXTS = new Set([
|
|
|
348
348
|
"png", "jpg", "jpeg", "gif", "webp", "bmp", "svg", "ico", "tiff", "heic",
|
|
349
349
|
]);
|
|
350
350
|
|
|
351
|
+
/** 默认隐藏的「非会话产出」文件(部署/插件/日志等混入工作区的杂项),
|
|
352
|
+
* 浏览页与输出文件面板都隐藏;可用 config.excludedWorkspaceNames 追加。 */
|
|
353
|
+
const WORKSPACE_EXCLUDED_NAMES = new Set([
|
|
354
|
+
"index.html", "index.htm", "serve.log", "docker-compose.yml", "docker-compose.yaml",
|
|
355
|
+
"compose.yml", "compose.yaml", "package.json", "package-lock.json", "pnpm-lock.yaml",
|
|
356
|
+
"yarn.lock", "bun.lockb", "Dockerfile", ".dockerignore", ".gitignore", ".npmrc",
|
|
357
|
+
"server.mjs", "server.js", "start.sh", "start-at-boot.sh",
|
|
358
|
+
]);
|
|
359
|
+
const WORKSPACE_EXCLUDED_SUFFIXES = [".log", ".lock"];
|
|
360
|
+
|
|
361
|
+
/** Whether a file name should be hidden from workspace listings (case-insensitive). */
|
|
362
|
+
function isExcludedWorkspaceName(name, extra = []) {
|
|
363
|
+
const lower = String(name).toLowerCase();
|
|
364
|
+
if (WORKSPACE_EXCLUDED_NAMES.has(lower)) return true;
|
|
365
|
+
for (const s of WORKSPACE_EXCLUDED_SUFFIXES) if (lower.endsWith(s)) return true;
|
|
366
|
+
for (const n of extra) if (lower === String(n).toLowerCase()) return true;
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
|
|
351
370
|
/** Collect subdirectories with their files, grouped by folder. */
|
|
352
|
-
async function collectGroups(root, excluded) {
|
|
371
|
+
async function collectGroups(root, excluded, hiddenNames = []) {
|
|
353
372
|
let entries;
|
|
354
373
|
try {
|
|
355
374
|
entries = await readdir(root, { withFileTypes: true });
|
|
@@ -375,6 +394,8 @@ async function collectGroups(root, excluded) {
|
|
|
375
394
|
if (item.isDirectory()) {
|
|
376
395
|
await walk(abs);
|
|
377
396
|
} else if (item.isFile()) {
|
|
397
|
+
// 隐藏明确的「非会话产出」文件(部署/插件/日志等),再走文档类白名单
|
|
398
|
+
if (isExcludedWorkspaceName(item.name, hiddenNames)) continue;
|
|
378
399
|
// 白名单:只显示文档类 + 图片文件,脚本/压缩包/代码/音视频一律隐藏
|
|
379
400
|
const ext = item.name.slice(item.name.lastIndexOf(".") + 1).toLowerCase();
|
|
380
401
|
if (!DOCUMENT_EXTS.has(ext)) continue;
|
|
@@ -590,6 +611,7 @@ export function createHandlers(options = {}) {
|
|
|
590
611
|
const maxFileBytes = positiveInteger(options.maxFileBytes, resolveMaxFileBytes());
|
|
591
612
|
const totalMaxBytes = positiveInteger(options.totalMaxBytes, resolveTotalMaxBytes());
|
|
592
613
|
const trustedHosts = Array.isArray(options.trustedHosts) ? [...options.trustedHosts] : [];
|
|
614
|
+
const hiddenNames = Array.isArray(options.excludedWorkspaceNames) ? [...options.excludedWorkspaceNames] : [];
|
|
593
615
|
const onError = typeof options.onError === "function" ? options.onError : undefined;
|
|
594
616
|
for (const entry of trustedHosts) assertTrustedAuthority(entry);
|
|
595
617
|
|
|
@@ -748,7 +770,7 @@ export function createHandlers(options = {}) {
|
|
|
748
770
|
methodNotAllowed(res, ["GET", "HEAD"]);
|
|
749
771
|
return;
|
|
750
772
|
}
|
|
751
|
-
const groups = await collectGroups(workspaceRoot, workspaceExcluded);
|
|
773
|
+
const groups = await collectGroups(workspaceRoot, workspaceExcluded, hiddenNames);
|
|
752
774
|
sendJson(res, 200, { ok: true, root: workspaceRoot, groups });
|
|
753
775
|
} catch (error) {
|
|
754
776
|
sendError(res, error, onError);
|
|
@@ -927,7 +949,7 @@ export function createHandlers(options = {}) {
|
|
|
927
949
|
const params = new URL(req.url || "/", "http://dsh.internal").searchParams;
|
|
928
950
|
const ws = params.get("ws") || "";
|
|
929
951
|
const all = params.get("all") === "1";
|
|
930
|
-
const groups = await collectGroups(workspaceRoot, workspaceExcluded);
|
|
952
|
+
const groups = await collectGroups(workspaceRoot, workspaceExcluded, hiddenNames);
|
|
931
953
|
const view = all ? groups : (ws ? groups.filter((g) => g.folder === ws) : groups);
|
|
932
954
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
|
|
933
955
|
res.end(workspaceBrowseHtml(view, ws, all));
|
|
@@ -1280,7 +1302,7 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1280
1302
|
const viewHref = previewable
|
|
1281
1303
|
? (isPdf ? `workspace-file?path=${rel}&inline=1` : isDocx ? `docx-preview?path=${rel}` : isPptx ? `pptx-preview?path=${rel}` : `workspace-preview?path=${rel}&from=list`)
|
|
1282
1304
|
: `workspace-file?path=${rel}&download=1`;
|
|
1283
|
-
return `<tr data-ts="${Math.floor(f.mtime)}">
|
|
1305
|
+
return `<tr data-ts="${Math.floor(f.mtime)}" data-name="${escapeHtml(f.name)}" data-size="${f.size}">
|
|
1284
1306
|
<td class="name"><a class="flink" href="${viewHref}" data-preview="${escapeHtml(viewHref)}" title="${escapeHtml(f.path)}">${escapeHtml(f.name)}</a></td>
|
|
1285
1307
|
<td class="size">${humanSize(f.size)}</td>
|
|
1286
1308
|
<td class="time">${humanTime(f.mtime)}</td>
|
|
@@ -1331,6 +1353,19 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1331
1353
|
.btn { display:inline-flex; align-items:center; gap:6px; background:var(--lp-btn-bg); color:var(--lp-btn-fg); text-decoration:none; border-radius:8px; padding:7px 14px; font-size:13px; flex:none; }
|
|
1332
1354
|
.btn:hover { background:var(--lp-btn-hover); }
|
|
1333
1355
|
.wrap { max-width:980px; margin:0 auto; padding:20px; }
|
|
1356
|
+
.radial { position:fixed; right:22px; bottom:22px; width:0; height:0; z-index:60; }
|
|
1357
|
+
.radial-center { position:absolute; left:0; top:0; transform:translate(-50%,-50%); width:52px; height:52px; border-radius:50%; border:1px solid var(--lp-border); background:var(--lp-bar-bg); color:var(--lp-fg); font-size:20px; line-height:1; cursor:pointer; display:flex; align-items:center; justify-content:center; box-shadow:var(--lp-shadow-lv2, 0 6px 18px rgba(0,0,0,.35)); }
|
|
1358
|
+
.radial-center:hover { border-color:var(--lp-accent); }
|
|
1359
|
+
.radial-item { position:absolute; left:0; top:0; transform:translate(-50%,-50%); display:flex; flex-direction:column; align-items:center; justify-content:center; gap:1px; width:52px; height:52px; border-radius:50%; border:1px solid var(--lp-border); background:var(--lp-bar-bg); color:var(--lp-fg); padding:0; font:inherit; cursor:pointer; opacity:0; pointer-events:none; transition:transform .18s ease, opacity .18s ease; }
|
|
1360
|
+
.radial-item b { font-size:15px; line-height:1; font-variant-numeric:tabular-nums; }
|
|
1361
|
+
.radial-item span { font-size:10px; color:var(--lp-meta); }
|
|
1362
|
+
.radial-item.on { border-color:var(--lp-accent); }
|
|
1363
|
+
.radial-item.on b { color:var(--lp-accent); }
|
|
1364
|
+
.radial.expanded .radial-item { opacity:1; pointer-events:auto; }
|
|
1365
|
+
.radial.expanded .ri-today { transform:translate(-50%,-50%) translate(0,-78px); }
|
|
1366
|
+
.radial.expanded .ri-week { transform:translate(-50%,-50%) translate(-56px,-56px); }
|
|
1367
|
+
.radial.expanded .ri-all { transform:translate(-50%,-50%) translate(-78px,0); }
|
|
1368
|
+
@media (max-width: 640px) { .radial { right:14px; bottom:14px; } .radial-center, .radial-item { width:46px; height:46px; } }
|
|
1334
1369
|
.group { margin-bottom:26px; }
|
|
1335
1370
|
.group h2 { font-size:15px; margin:0 0 8px; color:var(--lp-h2); }
|
|
1336
1371
|
.cnt { color:var(--lp-meta); font-size:12px; font-weight:400; }
|
|
@@ -1339,8 +1374,7 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1339
1374
|
.group.collapsed .gtoggle:before { content:'▸'; }
|
|
1340
1375
|
.group.collapsed table { display:none; }
|
|
1341
1376
|
.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; }
|
|
1377
|
+
.daybox { display:inline-flex; align-items:center; gap:8px; flex:none; } .daylabel { color:var(--lp-meta); font-size:13px; }
|
|
1344
1378
|
.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
1379
|
.dfilter:hover { border-color:var(--lp-accent); }
|
|
1346
1380
|
.dfilter.has-value { border-color:var(--lp-accent); }
|
|
@@ -1349,6 +1383,15 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1349
1383
|
.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
1384
|
.dfilter-clear:hover { color:var(--lp-fg); }
|
|
1351
1385
|
.dfilter-native { position:absolute; inset:0; width:100%; height:100%; opacity:0; cursor:pointer; pointer-events:auto; z-index:1; }
|
|
1386
|
+
.fsearch-wrap { position:relative; display:inline-flex; align-items:center; flex:none; }
|
|
1387
|
+
.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; }
|
|
1388
|
+
.fsearch-btn:hover { background:var(--lp-btn-hover); }
|
|
1389
|
+
.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); }
|
|
1390
|
+
.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; }
|
|
1391
|
+
.fsearch::placeholder { color:var(--lp-meta); }
|
|
1392
|
+
.fsearch:focus { outline:none; border-color:var(--lp-accent); }
|
|
1393
|
+
.fsearch-clear { background:transparent; border:none; color:var(--lp-meta); font-size:13px; line-height:1; cursor:pointer; padding:0 3px; }
|
|
1394
|
+
.fsearch-clear:hover { color:var(--lp-fg); }
|
|
1352
1395
|
.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
1396
|
.btn2:hover { background:var(--lp-btn-hover); }
|
|
1354
1397
|
.daybtn.active { background:#2563eb; color:#fff; }
|
|
@@ -1399,13 +1442,26 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1399
1442
|
<input type="date" id="dayFilter" class="dfilter-native" aria-label="按日期筛选">
|
|
1400
1443
|
</span>
|
|
1401
1444
|
<button type="button" class="btn2 daybtn" id="dayToday">今天</button>
|
|
1445
|
+
<span class="fsearch-wrap" id="fsearchWrap">
|
|
1446
|
+
<button type="button" class="fsearch-btn" id="fsearchBtn" title="搜索文件名" aria-label="搜索文件名">🔍</button>
|
|
1447
|
+
<span class="fsearch-box" id="fsearchBox" style="display:none">
|
|
1448
|
+
<input type="text" class="fsearch" id="fsearch" placeholder="搜索文件名…" aria-label="搜索文件名">
|
|
1449
|
+
<button type="button" class="fsearch-clear" id="fsearchClear" title="清除" aria-label="清除搜索">✕</button>
|
|
1450
|
+
</span>
|
|
1451
|
+
</span>
|
|
1402
1452
|
</span>
|
|
1403
1453
|
<span class="spacer"></span>
|
|
1404
1454
|
<a class="btn" href="${refreshHref}">⟳ 刷新</a>
|
|
1405
1455
|
</div>
|
|
1406
1456
|
<div class="wrap">
|
|
1407
1457
|
${groups.map(section).join("") || `<p class="empty">${ws && !all ? `工作区「${escapeHtml(ws)}」还没有文件` : "工作区还没有文件"}</p>`}
|
|
1408
|
-
<p class="empty" id="filterEmpty" style="display:none"
|
|
1458
|
+
<p class="empty" id="filterEmpty" style="display:none">没有匹配的文件</p>
|
|
1459
|
+
</div>
|
|
1460
|
+
<div class="radial" id="radial">
|
|
1461
|
+
<button type="button" class="radial-center" id="radialBtn" title="工作区概览" aria-label="工作区概览">📊</button>
|
|
1462
|
+
<button type="button" class="radial-item ri-today" id="radialToday" title="只看今天生成的文件"><b id="dashTodayN">–</b><span>今日</span></button>
|
|
1463
|
+
<button type="button" class="radial-item ri-week" id="radialWeek" title="只看最近7天生成的文件"><b id="dashWeekN">–</b><span>本周</span></button>
|
|
1464
|
+
<button type="button" class="radial-item ri-all" id="radialAll" title="显示全部文件"><b id="dashTotalN">–</b><span>全部</span></button>
|
|
1409
1465
|
</div>
|
|
1410
1466
|
<script>
|
|
1411
1467
|
// 预览链接点击 → 通知父窗口(wsOverlay)打开,父窗口用 embed/iframe 渲染,
|
|
@@ -1441,7 +1497,14 @@ document.addEventListener('click', function (e) {
|
|
|
1441
1497
|
var dfilter = document.getElementById('dfilter');
|
|
1442
1498
|
var dfilterText = document.getElementById('dfilterText');
|
|
1443
1499
|
var dfilterClear = document.getElementById('dfilterClear');
|
|
1500
|
+
var fsearch = document.getElementById('fsearch');
|
|
1444
1501
|
var filterEmpty = document.getElementById('filterEmpty');
|
|
1502
|
+
// 预览后返回不丢检索:用 sessionStorage 记住检索词与检索框开/关状态。
|
|
1503
|
+
var savedSearch = '';
|
|
1504
|
+
var savedOpen = false;
|
|
1505
|
+
try { savedSearch = sessionStorage.getItem('wsb-search') || ''; } catch (e) {}
|
|
1506
|
+
try { savedOpen = sessionStorage.getItem('wsb-search-open') === '1'; } catch (e) {}
|
|
1507
|
+
if (fsearch) fsearch.value = savedSearch;
|
|
1445
1508
|
var metaEl = document.querySelector('.bar .meta');
|
|
1446
1509
|
var origMeta = metaEl ? metaEl.textContent : '';
|
|
1447
1510
|
function pad(v) { return v < 10 ? '0' + v : v; }
|
|
@@ -1449,18 +1512,28 @@ document.addEventListener('click', function (e) {
|
|
|
1449
1512
|
var d = new Date(ts);
|
|
1450
1513
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
|
1451
1514
|
}
|
|
1515
|
+
function matchName(tr) {
|
|
1516
|
+
var q = (fsearch && fsearch.value || '').trim().toLowerCase();
|
|
1517
|
+
if (!q) return true;
|
|
1518
|
+
var hay = String(tr.dataset.name || '').toLowerCase();
|
|
1519
|
+
return q.split(/\s+/).filter(Boolean).every(function (tok) { return hay.indexOf(tok) !== -1; });
|
|
1520
|
+
}
|
|
1452
1521
|
// 预计算每行本地日期
|
|
1453
1522
|
Array.prototype.forEach.call(document.querySelectorAll('tr[data-ts]'), function (tr) {
|
|
1454
1523
|
tr.dataset.date = fmtDate(Number(tr.dataset.ts));
|
|
1455
1524
|
});
|
|
1456
1525
|
var todayStr = fmtDate(Date.now());
|
|
1526
|
+
var weekOn = false; // 近7天范围筛选
|
|
1457
1527
|
function apply() {
|
|
1458
1528
|
var sel = dayInput.value;
|
|
1529
|
+
var nowTs = Date.now();
|
|
1459
1530
|
var visTotal = 0;
|
|
1460
1531
|
groups.forEach(function (g) {
|
|
1461
1532
|
var vis = 0;
|
|
1462
1533
|
Array.prototype.forEach.call(g.querySelectorAll('tr[data-date]'), function (tr) {
|
|
1463
|
-
var
|
|
1534
|
+
var dayOk = !sel || tr.dataset.date === sel;
|
|
1535
|
+
var weekOk = !weekOn || (nowTs - Number(tr.dataset.ts)) <= 7 * 86400000;
|
|
1536
|
+
var ok = dayOk && weekOk && matchName(tr);
|
|
1464
1537
|
tr.style.display = ok ? '' : 'none';
|
|
1465
1538
|
if (ok) vis++;
|
|
1466
1539
|
});
|
|
@@ -1469,12 +1542,14 @@ document.addEventListener('click', function (e) {
|
|
|
1469
1542
|
if (vis === 0) g.classList.add('filtered-empty'); else g.classList.remove('filtered-empty');
|
|
1470
1543
|
visTotal += vis;
|
|
1471
1544
|
});
|
|
1472
|
-
|
|
1545
|
+
var searching = fsearch && fsearch.value.trim();
|
|
1546
|
+
var label = sel ? ('筛选 ' + sel) : (weekOn ? '近7日' : '');
|
|
1547
|
+
if (metaEl) metaEl.textContent = (label || searching) ? ((label || '全部') + (searching ? ' · ' + searching : '') + ' · ' + visTotal + ' 个文件') : origMeta;
|
|
1473
1548
|
if (dfilterText) dfilterText.textContent = sel || '选择日期';
|
|
1474
1549
|
if (dfilterClear) dfilterClear.style.display = sel ? 'inline-block' : 'none';
|
|
1475
1550
|
if (dfilter) dfilter.classList.toggle('has-value', !!sel);
|
|
1476
1551
|
if (dayToday) dayToday.classList.toggle('active', sel === todayStr);
|
|
1477
|
-
if (filterEmpty) filterEmpty.style.display = (
|
|
1552
|
+
if (filterEmpty) filterEmpty.style.display = ((label || searching) && visTotal === 0) ? 'block' : 'none';
|
|
1478
1553
|
}
|
|
1479
1554
|
// 打开日期选择器:整个控件点击 → showPicker(兜底 focus);点 ✕ 清除(阻止冒泡)。
|
|
1480
1555
|
if (dfilter) dfilter.addEventListener('click', function () {
|
|
@@ -1485,9 +1560,66 @@ document.addEventListener('click', function (e) {
|
|
|
1485
1560
|
});
|
|
1486
1561
|
if (dayInput) dayInput.addEventListener('input', apply);
|
|
1487
1562
|
if (dayInput) dayInput.addEventListener('change', apply);
|
|
1563
|
+
if (fsearch) fsearch.addEventListener('input', function () { try { sessionStorage.setItem('wsb-search', fsearch.value); } catch (e) {} apply(); });
|
|
1564
|
+
// 🔍 搜索弹窗:点击图标开/关,点✕清除,点击外部关闭。
|
|
1565
|
+
var fsearchBtn = document.getElementById('fsearchBtn');
|
|
1566
|
+
var fsearchBox = document.getElementById('fsearchBox');
|
|
1567
|
+
var fsearchClear = document.getElementById('fsearchClear');
|
|
1568
|
+
var fsearchWrap = document.getElementById('fsearchWrap');
|
|
1569
|
+
var fsearchOpen = false;
|
|
1570
|
+
function setFsearchOpen(open) {
|
|
1571
|
+
fsearchOpen = open;
|
|
1572
|
+
try { sessionStorage.setItem('wsb-search-open', open ? '1' : '0'); } catch (e) {}
|
|
1573
|
+
if (fsearchBox) {
|
|
1574
|
+
fsearchBox.style.display = open ? 'flex' : 'none';
|
|
1575
|
+
if (open && fsearchBtn) {
|
|
1576
|
+
var r = fsearchBtn.getBoundingClientRect();
|
|
1577
|
+
fsearchBox.style.top = (r.bottom + 8) + 'px';
|
|
1578
|
+
fsearchBox.style.left = Math.max(8, Math.min(r.left, (window.innerWidth || 0) - 280)) + 'px';
|
|
1579
|
+
}
|
|
1580
|
+
}
|
|
1581
|
+
if (open && fsearch) { try { fsearch.focus(); } catch (e) {} }
|
|
1582
|
+
}
|
|
1583
|
+
if (fsearchBtn) fsearchBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); setFsearchOpen(!fsearchOpen); });
|
|
1584
|
+
if (fsearchClear) fsearchClear.addEventListener('mousedown', function (e) { e.preventDefault(); e.stopPropagation(); });
|
|
1585
|
+
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); });
|
|
1488
1586
|
if (dayToday) dayToday.addEventListener('click', function () { dayInput.value = todayStr; apply(); });
|
|
1489
1587
|
if (dfilterClear) dfilterClear.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); dayInput.value = ''; apply(); });
|
|
1588
|
+
if (savedOpen) setFsearchOpen(true);
|
|
1490
1589
|
apply();
|
|
1590
|
+
|
|
1591
|
+
// 工作区概览(仪表盘):从各文件行的 mtime/size 统计 今日/7日/全部/总大小。
|
|
1592
|
+
(function () {
|
|
1593
|
+
var rows = Array.prototype.slice.call(document.querySelectorAll('tr[data-ts]'));
|
|
1594
|
+
var now = Date.now();
|
|
1595
|
+
var today = 0, week = 0, size = 0;
|
|
1596
|
+
rows.forEach(function (tr) {
|
|
1597
|
+
var ts = Number(tr.dataset.ts) || 0;
|
|
1598
|
+
if (tr.dataset.date === todayStr) today++;
|
|
1599
|
+
if (now - ts <= 7 * 86400000) week++;
|
|
1600
|
+
size += Number(tr.dataset.size) || 0;
|
|
1601
|
+
});
|
|
1602
|
+
function setTxt(id, v) { var el = document.getElementById(id); if (el) el.textContent = v; }
|
|
1603
|
+
setTxt('dashTodayN', today);
|
|
1604
|
+
setTxt('dashWeekN', week);
|
|
1605
|
+
setTxt('dashTotalN', rows.length);
|
|
1606
|
+
var radial = document.getElementById('radial');
|
|
1607
|
+
var radialBtn = document.getElementById('radialBtn');
|
|
1608
|
+
var radToday = document.getElementById('radialToday');
|
|
1609
|
+
var radWeek = document.getElementById('radialWeek');
|
|
1610
|
+
var radAll = document.getElementById('radialAll');
|
|
1611
|
+
if (radialBtn) radialBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); if (radial) radial.classList.toggle('expanded'); });
|
|
1612
|
+
function syncDashActive() {
|
|
1613
|
+
var sel = dayInput.value;
|
|
1614
|
+
if (radToday) radToday.classList.toggle('on', !weekOn && sel === todayStr);
|
|
1615
|
+
if (radWeek) radWeek.classList.toggle('on', weekOn);
|
|
1616
|
+
if (radAll) radAll.classList.toggle('on', !weekOn && !sel);
|
|
1617
|
+
}
|
|
1618
|
+
if (radToday) radToday.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); weekOn = false; dayInput.value = todayStr; apply(); syncDashActive(); });
|
|
1619
|
+
if (radWeek) radWeek.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); weekOn = true; dayInput.value = ''; apply(); syncDashActive(); });
|
|
1620
|
+
if (radAll) radAll.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); weekOn = false; dayInput.value = ''; apply(); syncDashActive(); });
|
|
1621
|
+
syncDashActive();
|
|
1622
|
+
})();
|
|
1491
1623
|
})();
|
|
1492
1624
|
</script>
|
|
1493
1625
|
</body>
|
|
@@ -1820,7 +1952,7 @@ export async function apply(ctx, config = {}) {
|
|
|
1820
1952
|
const skillsRoot = resolve(config.skillsRoot ?? DEFAULT_SKILLS_ROOT);
|
|
1821
1953
|
const onError = (error) => ctx.logger.error(error instanceof Error ? error : new Error(String(error)));
|
|
1822
1954
|
|
|
1823
|
-
const handlers = createHandlers({ trustedHosts, onError });
|
|
1955
|
+
const handlers = createHandlers({ trustedHosts, onError, excludedWorkspaceNames: config.excludedWorkspaceNames });
|
|
1824
1956
|
|
|
1825
1957
|
// ---- md2docx 工具: Markdown → Word (.docx, 带页码) ----
|
|
1826
1958
|
// 调用 md2docx.py (pandoc-free, python-docx + 页脚 PAGE 字段)。
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
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",
|