dsh-long-plugins 1.3.6 → 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 +3 -3
- package/client/client.js +128 -30
- package/dsh.plugin.json +1 -1
- package/lib/index.js +59 -5
- 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 自动刷新) |
|
package/client/client.js
CHANGED
|
@@ -573,6 +573,14 @@ window.__ModuleLoader__.load({
|
|
|
573
573
|
return d.getFullYear() + '-' + p(d.getMonth() + 1) + '-' + p(d.getDate())
|
|
574
574
|
}
|
|
575
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
|
+
|
|
576
584
|
/** 自定义日期筛选控件:显示 📅 + 文字,点击/触摸打开系统日期选择器,自带 ✕ 清除。
|
|
577
585
|
* 原生 input[type=date] 在手机端是空框、无图标;此处整个控件包一层 onClick,
|
|
578
586
|
* 统一调用 input.showPicker(),故点图标/文字/任意位置都触发(桌面/手机一致)。 */
|
|
@@ -600,6 +608,39 @@ window.__ModuleLoader__.load({
|
|
|
600
608
|
)
|
|
601
609
|
}
|
|
602
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
|
+
|
|
603
644
|
function UploadSettingsSection() {
|
|
604
645
|
const [state, setState] = React.useState({
|
|
605
646
|
loading: true,
|
|
@@ -614,6 +655,7 @@ window.__ModuleLoader__.load({
|
|
|
614
655
|
const [preview, setPreview] = React.useState(null)
|
|
615
656
|
const [previewMaximized, setPreviewMaximized] = React.useState(false)
|
|
616
657
|
const [dayFilter, setDayFilter] = React.useState('')
|
|
658
|
+
const [search, setSearch] = React.useState('')
|
|
617
659
|
|
|
618
660
|
async function refresh() {
|
|
619
661
|
setState((current) => ({ ...current, loading: true, error: '' }))
|
|
@@ -720,9 +762,9 @@ window.__ModuleLoader__.load({
|
|
|
720
762
|
for (const day of days) groups.push({ day, files: byDay.get(day) })
|
|
721
763
|
return groups
|
|
722
764
|
}
|
|
723
|
-
//
|
|
724
|
-
const shownFiles = dayFilter
|
|
725
|
-
? state.files.filter((f) => { try { return localDay(f.modifiedAt) === dayFilter } catch { return false } })
|
|
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))
|
|
726
768
|
: state.files
|
|
727
769
|
const dateGroups = groupFilesByDate(shownFiles)
|
|
728
770
|
|
|
@@ -741,6 +783,7 @@ window.__ModuleLoader__.load({
|
|
|
741
783
|
React.createElement(
|
|
742
784
|
'div',
|
|
743
785
|
{ className: 'dsh-upload-head-actions' },
|
|
786
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
744
787
|
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
745
788
|
React.createElement(
|
|
746
789
|
'button',
|
|
@@ -765,8 +808,8 @@ window.__ModuleLoader__.load({
|
|
|
765
808
|
!state.loading && state.files.length === 0
|
|
766
809
|
? React.createElement('div', { className: 'dsh-upload-empty' }, '当前没有已上传文件。')
|
|
767
810
|
: null,
|
|
768
|
-
!state.loading && dayFilter !== '' && shownFiles.length === 0 && state.files.length > 0
|
|
769
|
-
? React.createElement('div', { className: 'dsh-upload-empty' }, '
|
|
811
|
+
!state.loading && (dayFilter !== '' || search !== '') && shownFiles.length === 0 && state.files.length > 0
|
|
812
|
+
? React.createElement('div', { className: 'dsh-upload-empty' }, '没有匹配的文件。')
|
|
770
813
|
: null,
|
|
771
814
|
preview
|
|
772
815
|
? React.createElement(
|
|
@@ -873,6 +916,14 @@ window.__ModuleLoader__.load({
|
|
|
873
916
|
.dsh-upload-settings{display:flex;flex-direction:column;gap:16px;min-width:0;padding:4px 2px 24px;color:var(--dsw-alias-label-primary)}
|
|
874
917
|
.dsh-upload-settings-head{display:flex;align-items:flex-start;justify-content:space-between;gap:16px}
|
|
875
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)}
|
|
876
927
|
.dsh-upload-settings h2{margin:0;font-size:20px;line-height:28px}
|
|
877
928
|
.dsh-upload-settings p{margin:4px 0 0;color:var(--dsw-alias-label-secondary);font-size:13px;line-height:20px}
|
|
878
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}
|
|
@@ -941,6 +992,7 @@ window.__ModuleLoader__.load({
|
|
|
941
992
|
const [busy, setBusy] = React.useState(false)
|
|
942
993
|
const [collapsed, setCollapsed] = React.useState({})
|
|
943
994
|
const [dayFilter, setDayFilter] = React.useState('')
|
|
995
|
+
const [search, setSearch] = React.useState('')
|
|
944
996
|
const [copied, setCopied] = React.useState(false)
|
|
945
997
|
const [editing, setEditing] = React.useState(false)
|
|
946
998
|
const [edited, setEdited] = React.useState('')
|
|
@@ -1124,9 +1176,12 @@ window.__ModuleLoader__.load({
|
|
|
1124
1176
|
}
|
|
1125
1177
|
|
|
1126
1178
|
// 日期筛选:按所选天过滤各组文件,隐藏空组(空日期=全部)。
|
|
1127
|
-
const shownGroups = (dayFilter && groups !== null)
|
|
1179
|
+
const shownGroups = ((dayFilter || search) && groups !== null)
|
|
1128
1180
|
? groups
|
|
1129
|
-
.map((g) => ({ folder: g.folder, files: g.files.filter((f) =>
|
|
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
|
+
) }))
|
|
1130
1185
|
.filter((g) => g.files.length > 0)
|
|
1131
1186
|
: groups
|
|
1132
1187
|
|
|
@@ -1135,12 +1190,13 @@ window.__ModuleLoader__.load({
|
|
|
1135
1190
|
{ style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
1136
1191
|
React.createElement('div', { style: { fontSize: 13, color: 'var(--dsw-alias-label-tertiary)' } }, '工作区输出文件(按文件夹分类,预览 / 下载 / 删除)'),
|
|
1137
1192
|
React.createElement('div', { style: { display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', minWidth: 0 } },
|
|
1193
|
+
React.createElement(SearchPopup, { value: search, onChange: setSearch, placeholder: '搜索文件名…' }),
|
|
1138
1194
|
React.createElement(DateFilter, { value: dayFilter, onChange: setDayFilter, placeholder: '选择日期' }),
|
|
1139
1195
|
),
|
|
1140
1196
|
error !== null && React.createElement('div', { style: { fontSize: 12, color: 'var(--dsw-alias-state-error-primary)' } }, error),
|
|
1141
1197
|
groups === null && error === null && React.createElement('div', { style: metaStyle }, '加载中…'),
|
|
1142
1198
|
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 }, '
|
|
1199
|
+
(dayFilter || search) && groups !== null && groups.length > 0 && shownGroups !== null && shownGroups.length === 0 && React.createElement('div', { style: metaStyle }, '没有匹配的文件'),
|
|
1144
1200
|
shownGroups !== null && shownGroups.map((group) => React.createElement(
|
|
1145
1201
|
'div',
|
|
1146
1202
|
{ key: group.folder, style: { display: 'flex', flexDirection: 'column', gap: 6, minWidth: 0 } },
|
|
@@ -1327,11 +1383,40 @@ window.__ModuleLoader__.load({
|
|
|
1327
1383
|
"files.maximize": "Maximize",
|
|
1328
1384
|
"files.restore": "Restore"
|
|
1329
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
|
+
}
|
|
1330
1414
|
function SkillsSection({ t }) {
|
|
1331
1415
|
const [groups, setGroups] = react.useState(null);
|
|
1332
1416
|
const [error, setError] = react.useState(null);
|
|
1333
1417
|
const [preview, setPreview] = react.useState(null);
|
|
1334
1418
|
const [collapsed, setCollapsed] = react.useState({});
|
|
1419
|
+
const [search, setSearch] = react.useState("");
|
|
1335
1420
|
const [maximized, setMaximized] = react.useState(false);
|
|
1336
1421
|
const [editing, setEditing] = react.useState(false);
|
|
1337
1422
|
const [edited, setEdited] = react.useState("");
|
|
@@ -1457,34 +1542,47 @@ window.__ModuleLoader__.load({
|
|
|
1457
1542
|
padding: "8px 12px", borderBottom: "1px solid var(--dsw-alias-border-l2)", flex: "none", flexWrap: "wrap"
|
|
1458
1543
|
};
|
|
1459
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
|
+
|
|
1460
1551
|
return react_jsx_runtime.jsxs("div", {
|
|
1461
1552
|
style: { display: "flex", flexDirection: "column", gap: 6, minWidth: 0 },
|
|
1462
1553
|
children: [
|
|
1463
|
-
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
|
+
] }),
|
|
1464
1558
|
error !== null && react_jsx_runtime.jsx("div", { style: { fontSize: 12, color: "var(--dsw-alias-state-error-primary)" }, children: error }),
|
|
1465
1559
|
groups === null && error === null && react_jsx_runtime.jsx("div", { style: metaStyle, children: t("files.loading") }),
|
|
1466
1560
|
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
|
-
|
|
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: "没有匹配的技能文档" }),
|
|
1488
1586
|
preview !== null && react_jsx_runtime.jsxs("div", {
|
|
1489
1587
|
style: overlayStyle,
|
|
1490
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 data-ts="${Math.floor(f.mtime)}">
|
|
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>
|
|
@@ -1349,6 +1349,15 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1349
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
1350
|
.dfilter-clear:hover { color:var(--lp-fg); }
|
|
1351
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); }
|
|
1352
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; }
|
|
1353
1362
|
.btn2:hover { background:var(--lp-btn-hover); }
|
|
1354
1363
|
.daybtn.active { background:#2563eb; color:#fff; }
|
|
@@ -1399,13 +1408,20 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1399
1408
|
<input type="date" id="dayFilter" class="dfilter-native" aria-label="按日期筛选">
|
|
1400
1409
|
</span>
|
|
1401
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>
|
|
1402
1418
|
</span>
|
|
1403
1419
|
<span class="spacer"></span>
|
|
1404
1420
|
<a class="btn" href="${refreshHref}">⟳ 刷新</a>
|
|
1405
1421
|
</div>
|
|
1406
1422
|
<div class="wrap">
|
|
1407
1423
|
${groups.map(section).join("") || `<p class="empty">${ws && !all ? `工作区「${escapeHtml(ws)}」还没有文件` : "工作区还没有文件"}</p>`}
|
|
1408
|
-
<p class="empty" id="filterEmpty" style="display:none"
|
|
1424
|
+
<p class="empty" id="filterEmpty" style="display:none">没有匹配的文件</p>
|
|
1409
1425
|
</div>
|
|
1410
1426
|
<script>
|
|
1411
1427
|
// 预览链接点击 → 通知父窗口(wsOverlay)打开,父窗口用 embed/iframe 渲染,
|
|
@@ -1441,7 +1457,14 @@ document.addEventListener('click', function (e) {
|
|
|
1441
1457
|
var dfilter = document.getElementById('dfilter');
|
|
1442
1458
|
var dfilterText = document.getElementById('dfilterText');
|
|
1443
1459
|
var dfilterClear = document.getElementById('dfilterClear');
|
|
1460
|
+
var fsearch = document.getElementById('fsearch');
|
|
1444
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;
|
|
1445
1468
|
var metaEl = document.querySelector('.bar .meta');
|
|
1446
1469
|
var origMeta = metaEl ? metaEl.textContent : '';
|
|
1447
1470
|
function pad(v) { return v < 10 ? '0' + v : v; }
|
|
@@ -1449,6 +1472,12 @@ document.addEventListener('click', function (e) {
|
|
|
1449
1472
|
var d = new Date(ts);
|
|
1450
1473
|
return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate());
|
|
1451
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
|
+
}
|
|
1452
1481
|
// 预计算每行本地日期
|
|
1453
1482
|
Array.prototype.forEach.call(document.querySelectorAll('tr[data-ts]'), function (tr) {
|
|
1454
1483
|
tr.dataset.date = fmtDate(Number(tr.dataset.ts));
|
|
@@ -1460,7 +1489,7 @@ document.addEventListener('click', function (e) {
|
|
|
1460
1489
|
groups.forEach(function (g) {
|
|
1461
1490
|
var vis = 0;
|
|
1462
1491
|
Array.prototype.forEach.call(g.querySelectorAll('tr[data-date]'), function (tr) {
|
|
1463
|
-
var ok = !sel || tr.dataset.date === sel;
|
|
1492
|
+
var ok = (!sel || tr.dataset.date === sel) && matchName(tr);
|
|
1464
1493
|
tr.style.display = ok ? '' : 'none';
|
|
1465
1494
|
if (ok) vis++;
|
|
1466
1495
|
});
|
|
@@ -1469,12 +1498,13 @@ document.addEventListener('click', function (e) {
|
|
|
1469
1498
|
if (vis === 0) g.classList.add('filtered-empty'); else g.classList.remove('filtered-empty');
|
|
1470
1499
|
visTotal += vis;
|
|
1471
1500
|
});
|
|
1472
|
-
|
|
1501
|
+
var searching = fsearch && fsearch.value.trim();
|
|
1502
|
+
if (metaEl) metaEl.textContent = (sel || searching) ? ('筛选 ' + (sel || '日期') + (searching ? ' · ' + searching : '') + ' · ' + visTotal + ' 个文件') : origMeta;
|
|
1473
1503
|
if (dfilterText) dfilterText.textContent = sel || '选择日期';
|
|
1474
1504
|
if (dfilterClear) dfilterClear.style.display = sel ? 'inline-block' : 'none';
|
|
1475
1505
|
if (dfilter) dfilter.classList.toggle('has-value', !!sel);
|
|
1476
1506
|
if (dayToday) dayToday.classList.toggle('active', sel === todayStr);
|
|
1477
|
-
if (filterEmpty) filterEmpty.style.display = (sel && visTotal === 0) ? 'block' : 'none';
|
|
1507
|
+
if (filterEmpty) filterEmpty.style.display = ((sel || searching) && visTotal === 0) ? 'block' : 'none';
|
|
1478
1508
|
}
|
|
1479
1509
|
// 打开日期选择器:整个控件点击 → showPicker(兜底 focus);点 ✕ 清除(阻止冒泡)。
|
|
1480
1510
|
if (dfilter) dfilter.addEventListener('click', function () {
|
|
@@ -1485,8 +1515,32 @@ document.addEventListener('click', function (e) {
|
|
|
1485
1515
|
});
|
|
1486
1516
|
if (dayInput) dayInput.addEventListener('input', apply);
|
|
1487
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); });
|
|
1488
1541
|
if (dayToday) dayToday.addEventListener('click', function () { dayInput.value = todayStr; apply(); });
|
|
1489
1542
|
if (dfilterClear) dfilterClear.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); dayInput.value = ''; apply(); });
|
|
1543
|
+
if (savedOpen) setFsearchOpen(true);
|
|
1490
1544
|
apply();
|
|
1491
1545
|
})();
|
|
1492
1546
|
</script>
|
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",
|