dsh-long-plugins 1.3.7 → 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 CHANGED
@@ -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
 
@@ -948,7 +995,7 @@ window.__ModuleLoader__.load({
948
995
  .dsh-upload-group-day span{font-weight:400;color:var(--dsw-alias-label-tertiary)}
949
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}
950
997
  .dsh-upload-file-name{flex:1;min-width:0;display:flex;align-items:center;gap:6px;color:var(--dsw-alias-label-primary)}
951
- .dsh-upload-file-label{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
998
+ .dsh-upload-file-label{min-width:0;white-space:normal;word-break:break-all;overflow-wrap:anywhere}
952
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}
953
1000
  .dsh-upload-file-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}
954
1001
  .dsh-upload-actions{display:flex;flex:none;gap:7px}
@@ -981,6 +1028,9 @@ window.__ModuleLoader__.load({
981
1028
  .dsh-upload-preview-open:hover{background:var(--dsw-alias-interactive-bg-hover)}
982
1029
  .dsh-upload-preview-img{max-width:100%;max-height:70vh;object-fit:contain;display:block}
983
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)}
984
1034
  `
985
1035
 
986
1036
  const inject = ['slots', 'sessions', 'inputTriggers', 'conversation', 'timer']
@@ -1139,7 +1189,7 @@ window.__ModuleLoader__.load({
1139
1189
  borderRadius: 8, border: '1px solid var(--dsw-alias-border-l2)',
1140
1190
  background: 'var(--dsw-alias-bg-module-platform, transparent)', fontSize: 13,
1141
1191
  }
1142
- const nameStyle = { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', color: 'var(--dsw-alias-label-primary)' }
1192
+ const nameStyle = { flex: 1, minWidth: 0, whiteSpace: 'normal', wordBreak: 'break-all', overflowWrap: 'anywhere', color: 'var(--dsw-alias-label-primary)' }
1143
1193
  const metaStyle = { flex: 'none', color: 'var(--dsw-alias-label-tertiary)', fontSize: 12 }
1144
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)' }
1145
1195
  const delStyle = { ...btnStyle, color: 'var(--dsw-alias-state-error-primary)' }
@@ -1217,7 +1267,7 @@ window.__ModuleLoader__.load({
1217
1267
  React.createElement(
1218
1268
  'span',
1219
1269
  { className: 'dsh-ws-name', style: { ...nameStyle, display: 'flex', alignItems: 'center', gap: 6 }, title: f.path },
1220
- React.createElement('span', { className: 'dsh-ws-file-label', style: { flex: 1, minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, basenameWithoutExt(f.path)),
1270
+ React.createElement('span', { className: 'dsh-ws-file-label', style: { flex: 1, minWidth: 0, whiteSpace: 'normal', wordBreak: 'break-all', overflowWrap: 'anywhere' } }, basenameWithoutExt(f.path)),
1221
1271
  React.createElement('span', { className: 'dsh-ws-file-type', style: { flex: 'none' } }, fileTypeLabel(f.path)),
1222
1272
  ),
1223
1273
  React.createElement('span', { className: 'dsh-ws-meta', style: metaStyle }, `${sizeText(f.size)} · ${dateText(f.mtime)}`),
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.7",
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)}" data-name="${escapeHtml(f.name)}">
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); }
@@ -1423,6 +1457,12 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
1423
1457
  ${groups.map(section).join("") || `<p class="empty">${ws && !all ? `工作区「${escapeHtml(ws)}」还没有文件` : "工作区还没有文件"}</p>`}
1424
1458
  <p class="empty" id="filterEmpty" style="display:none">没有匹配的文件</p>
1425
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>
1465
+ </div>
1426
1466
  <script>
1427
1467
  // 预览链接点击 → 通知父窗口(wsOverlay)打开,父窗口用 embed/iframe 渲染,
1428
1468
  // 避免在嵌套 iframe 内直接导航导致 PDF 无法滚动。
@@ -1483,13 +1523,17 @@ document.addEventListener('click', function (e) {
1483
1523
  tr.dataset.date = fmtDate(Number(tr.dataset.ts));
1484
1524
  });
1485
1525
  var todayStr = fmtDate(Date.now());
1526
+ var weekOn = false; // 近7天范围筛选
1486
1527
  function apply() {
1487
1528
  var sel = dayInput.value;
1529
+ var nowTs = Date.now();
1488
1530
  var visTotal = 0;
1489
1531
  groups.forEach(function (g) {
1490
1532
  var vis = 0;
1491
1533
  Array.prototype.forEach.call(g.querySelectorAll('tr[data-date]'), function (tr) {
1492
- var ok = (!sel || tr.dataset.date === sel) && matchName(tr);
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);
1493
1537
  tr.style.display = ok ? '' : 'none';
1494
1538
  if (ok) vis++;
1495
1539
  });
@@ -1499,12 +1543,13 @@ document.addEventListener('click', function (e) {
1499
1543
  visTotal += vis;
1500
1544
  });
1501
1545
  var searching = fsearch && fsearch.value.trim();
1502
- if (metaEl) metaEl.textContent = (sel || searching) ? ('筛选 ' + (sel || '日期') + (searching ? ' · ' + searching : '') + ' · ' + visTotal + ' 个文件') : origMeta;
1546
+ var label = sel ? ('筛选 ' + sel) : (weekOn ? '近7日' : '');
1547
+ if (metaEl) metaEl.textContent = (label || searching) ? ((label || '全部') + (searching ? ' · ' + searching : '') + ' · ' + visTotal + ' 个文件') : origMeta;
1503
1548
  if (dfilterText) dfilterText.textContent = sel || '选择日期';
1504
1549
  if (dfilterClear) dfilterClear.style.display = sel ? 'inline-block' : 'none';
1505
1550
  if (dfilter) dfilter.classList.toggle('has-value', !!sel);
1506
1551
  if (dayToday) dayToday.classList.toggle('active', sel === todayStr);
1507
- if (filterEmpty) filterEmpty.style.display = ((sel || searching) && visTotal === 0) ? 'block' : 'none';
1552
+ if (filterEmpty) filterEmpty.style.display = ((label || searching) && visTotal === 0) ? 'block' : 'none';
1508
1553
  }
1509
1554
  // 打开日期选择器:整个控件点击 → showPicker(兜底 focus);点 ✕ 清除(阻止冒泡)。
1510
1555
  if (dfilter) dfilter.addEventListener('click', function () {
@@ -1542,6 +1587,39 @@ document.addEventListener('click', function (e) {
1542
1587
  if (dfilterClear) dfilterClear.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); dayInput.value = ''; apply(); });
1543
1588
  if (savedOpen) setFsearchOpen(true);
1544
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
+ })();
1545
1623
  })();
1546
1624
  </script>
1547
1625
  </body>
@@ -1874,7 +1952,7 @@ export async function apply(ctx, config = {}) {
1874
1952
  const skillsRoot = resolve(config.skillsRoot ?? DEFAULT_SKILLS_ROOT);
1875
1953
  const onError = (error) => ctx.logger.error(error instanceof Error ? error : new Error(String(error)));
1876
1954
 
1877
- const handlers = createHandlers({ trustedHosts, onError });
1955
+ const handlers = createHandlers({ trustedHosts, onError, excludedWorkspaceNames: config.excludedWorkspaceNames });
1878
1956
 
1879
1957
  // ---- md2docx 工具: Markdown → Word (.docx, 带页码) ----
1880
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.7",
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",