dsh-long-plugins 1.3.8 → 1.3.9
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/client/client.js +27 -1
- package/dsh.plugin.json +1 -1
- package/lib/index.js +79 -2
- package/package.json +1 -1
package/client/client.js
CHANGED
|
@@ -95,6 +95,21 @@ window.__ModuleLoader__.load({
|
|
|
95
95
|
t._timer = setTimeout(() => { t.className = 'dsh-long-toast' }, 2800)
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/** 复制文本到剪贴板 + 轻提示。 */
|
|
99
|
+
function copyText(text) {
|
|
100
|
+
try {
|
|
101
|
+
const done = () => showToast(`已复制:${text}`)
|
|
102
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
103
|
+
navigator.clipboard.writeText(text).then(done, () => showToast('复制失败', 'error'))
|
|
104
|
+
} else {
|
|
105
|
+
const ta = document.createElement('textarea')
|
|
106
|
+
ta.value = text; ta.style.position = 'fixed'; ta.style.opacity = '0'
|
|
107
|
+
document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta)
|
|
108
|
+
done()
|
|
109
|
+
}
|
|
110
|
+
} catch (e) { showToast('复制失败', 'error') }
|
|
111
|
+
}
|
|
112
|
+
|
|
98
113
|
/** Trigger a browser download for a URL (no navigation, keeps the page). */
|
|
99
114
|
function triggerDownload(url, name) {
|
|
100
115
|
const a = document.createElement('a')
|
|
@@ -928,6 +943,15 @@ window.__ModuleLoader__.load({
|
|
|
928
943
|
{ className: 'dsh-upload-actions' },
|
|
929
944
|
React.createElement('button', { type: 'button', className: 'dsh-upload-preview', onClick: () => previewFile(file.name) }, '预览'),
|
|
930
945
|
React.createElement('a', { href: downloadUrl(file.name), download: file.name }, '下载'),
|
|
946
|
+
React.createElement(
|
|
947
|
+
'button',
|
|
948
|
+
{ type: 'button', onClick: () => {
|
|
949
|
+
const s = String(file.path || file.name)
|
|
950
|
+
const base = state.root ? String(state.root).replace(/\/[^/]*$/, '') : ''
|
|
951
|
+
copyText(base && s.indexOf(base) === 0 ? s.slice(base.length).replace(/^\/+/, '') : s)
|
|
952
|
+
} },
|
|
953
|
+
'复制路径',
|
|
954
|
+
),
|
|
931
955
|
React.createElement(
|
|
932
956
|
'button',
|
|
933
957
|
{ type: 'button', disabled: deleting === file.name, onClick: () => remove(file.name) },
|
|
@@ -1000,7 +1024,8 @@ window.__ModuleLoader__.load({
|
|
|
1000
1024
|
.dsh-upload-file-meta{flex:none;color:var(--dsw-alias-label-tertiary);font-size:12px}
|
|
1001
1025
|
.dsh-upload-actions{display:flex;flex:none;gap:7px}
|
|
1002
1026
|
.dsh-upload-actions button{color:var(--dsw-alias-state-error-primary)}
|
|
1003
|
-
|
|
1027
|
+
.dsh-upload-actions a,.dsh-upload-actions button{white-space:nowrap}
|
|
1028
|
+
@media (max-width:640px){.dsh-upload-row{align-items:stretch;flex-direction:column;gap:4px}.dsh-upload-file-name{white-space:normal;word-break:break-all}.dsh-upload-actions{width:100%;display:flex;gap:6px}.dsh-upload-actions a,.dsh-upload-actions button{flex:1;text-align:center;white-space:nowrap;padding:6px 4px}.dsh-upload-chip{min-width:160px}.dsh-upload-settings-head{flex-direction:column;align-items:stretch;gap:10px}.dsh-upload-head-actions{width:100%;justify-content:flex-end}.dsh-upload-head-actions .dsh-upload-refresh{flex:1;text-align:center;padding:7px 8px;max-width:none}}
|
|
1004
1029
|
.dsh-ws-folder:hover{background:var(--dsw-alias-interactive-bg-hover)}
|
|
1005
1030
|
.dsh-ws-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}
|
|
1006
1031
|
.dsh-upload-preview-del{color:var(--dsw-alias-state-error-primary)!important}
|
|
@@ -1276,6 +1301,7 @@ window.__ModuleLoader__.load({
|
|
|
1276
1301
|
{ className: 'dsh-ws-actions', style: { display: 'flex', gap: 6, flex: 'none' } },
|
|
1277
1302
|
React.createElement('button', { type: 'button', style: btnStyle, disabled: busy, onClick: () => openPreview(f.path) }, '预览'),
|
|
1278
1303
|
React.createElement('a', { href: '/api/dsh-uploads/workspace-file?path=' + encodeURIComponent(f.path) + '&download=1', download: f.name, style: btnStyle }, '下载'),
|
|
1304
|
+
React.createElement('button', { type: 'button', style: btnStyle, disabled: busy, onClick: () => copyText(f.path) }, '复制路径'),
|
|
1279
1305
|
React.createElement('button', { type: 'button', style: delStyle, disabled: busy, onClick: () => doDelete(f.path) }, '删除'),
|
|
1280
1306
|
),
|
|
1281
1307
|
)),
|
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.9",
|
|
5
5
|
"entry": {
|
|
6
6
|
"name": "dsh-long-plugins",
|
|
7
7
|
"inject": [
|
package/lib/index.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { createReadStream } from "node:fs";
|
|
16
16
|
import { readFile, writeFile } from "node:fs/promises";
|
|
17
|
-
import { link, lstat, mkdir, open, readdir, stat, unlink } from "node:fs/promises";
|
|
17
|
+
import { link, lstat, mkdir, open, readdir, rename, stat, unlink } from "node:fs/promises";
|
|
18
18
|
import { homedir } from "node:os";
|
|
19
19
|
import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
|
|
20
20
|
import { fileURLToPath } from "node:url";
|
|
@@ -865,6 +865,34 @@ export function createHandlers(options = {}) {
|
|
|
865
865
|
}
|
|
866
866
|
};
|
|
867
867
|
|
|
868
|
+
// 重命名工作区文件:只改文件名(限定同目录,不跨目录移动),低风险。
|
|
869
|
+
const workspaceRename = async (req, res) => {
|
|
870
|
+
try {
|
|
871
|
+
requireTrusted(req);
|
|
872
|
+
if (req.method !== "POST") {
|
|
873
|
+
methodNotAllowed(res, ["POST"]);
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const body = await readJsonBody(req);
|
|
877
|
+
const rel = typeof body === "object" && body !== null ? body.path : undefined;
|
|
878
|
+
const newName = typeof body === "object" && body !== null ? body.newName : undefined;
|
|
879
|
+
const full = safeResolve(workspaceRoot, rel);
|
|
880
|
+
if (full === undefined) throw new HttpError(400, "invalid path");
|
|
881
|
+
const info = await stat(full);
|
|
882
|
+
if (!info.isFile()) throw new HttpError(400, "not a regular file");
|
|
883
|
+
// 新文件名必须是安全的纯文件名(无分隔符/路径穿越/隐藏/特殊)
|
|
884
|
+
if (!isSafeStoredName(newName)) throw new HttpError(400, "invalid name");
|
|
885
|
+
const newRel = join(dirname(rel), newName);
|
|
886
|
+
const newFull = safeResolve(workspaceRoot, newRel);
|
|
887
|
+
if (newFull === undefined || newFull === full) throw new HttpError(400, "invalid target");
|
|
888
|
+
// 同目录纯改名:rename 原子且安全(不跨目录移动)
|
|
889
|
+
await rename(full, newFull);
|
|
890
|
+
sendJson(res, 200, { ok: true, path: newRel });
|
|
891
|
+
} catch (error) {
|
|
892
|
+
sendError(res, error, onError);
|
|
893
|
+
}
|
|
894
|
+
};
|
|
895
|
+
|
|
868
896
|
const workspaceSave = async (req, res) => {
|
|
869
897
|
try {
|
|
870
898
|
requireTrusted(req);
|
|
@@ -1242,7 +1270,7 @@ window.addEventListener('resize',function(){ try{ if(fitMode) zoomFit(); else ap
|
|
|
1242
1270
|
}
|
|
1243
1271
|
};
|
|
1244
1272
|
|
|
1245
|
-
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage };
|
|
1273
|
+
return { root, maxFileBytes, totalMaxBytes, api, download, preview, workspaceList, workspaceFile, workspacePreview, workspaceBrowse, workspaceDelete, workspaceRename, workspaceSave, docxPreviewPage, docxPreviewAsset, pptxPreviewPage };
|
|
1246
1274
|
}
|
|
1247
1275
|
|
|
1248
1276
|
/** 人类可读文件大小。 */
|
|
@@ -1309,6 +1337,8 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1309
1337
|
<td class="acts">
|
|
1310
1338
|
${previewable ? `<a class="tag" href="${viewHref}" data-preview="${escapeHtml(viewHref)}">${ICON_EYE} 预览</a>` : `<a class="tag" href="${viewHref}">${ICON_DL} 下载</a>`}
|
|
1311
1339
|
<a class="tag dl" href="workspace-file?path=${rel}&download=1">${ICON_DL} 下载</a>
|
|
1340
|
+
<button type="button" class="tag" data-rename="${escapeHtml(f.path)}">重命名</button>
|
|
1341
|
+
<button type="button" class="tag" data-copypath="${escapeHtml(f.path)}">复制路径</button>
|
|
1312
1342
|
</td>
|
|
1313
1343
|
</tr>`;
|
|
1314
1344
|
}).join("");
|
|
@@ -1392,6 +1422,9 @@ function workspaceBrowseHtml(groups, ws = "", all = false) {
|
|
|
1392
1422
|
.fsearch:focus { outline:none; border-color:var(--lp-accent); }
|
|
1393
1423
|
.fsearch-clear { background:transparent; border:none; color:var(--lp-meta); font-size:13px; line-height:1; cursor:pointer; padding:0 3px; }
|
|
1394
1424
|
.fsearch-clear:hover { color:var(--lp-fg); }
|
|
1425
|
+
.wsb-toast { position:fixed; left:50%; bottom:32px; transform:translateX(-50%) translateY(12px); z-index:2000; max-width:min(90vw,520px); padding:10px 16px; border-radius:10px; background:var(--lp-bar-bg); border:1px solid var(--lp-border); color:var(--lp-fg); font-size:13px; line-height:20px; opacity:0; pointer-events:none; transition:opacity .2s ease,transform .2s ease; box-shadow:0 10px 30px rgba(0,0,0,.35); }
|
|
1426
|
+
.wsb-toast.show { opacity:1; transform:translateX(-50%) translateY(0); }
|
|
1427
|
+
.wsb-toast.error { border-color:#e25050; color:#ffb4b4; }
|
|
1395
1428
|
.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; }
|
|
1396
1429
|
.btn2:hover { background:var(--lp-btn-hover); }
|
|
1397
1430
|
.daybtn.active { background:#2563eb; color:#fff; }
|
|
@@ -1588,6 +1621,44 @@ document.addEventListener('click', function (e) {
|
|
|
1588
1621
|
if (savedOpen) setFsearchOpen(true);
|
|
1589
1622
|
apply();
|
|
1590
1623
|
|
|
1624
|
+
// 重命名 / 复制路径
|
|
1625
|
+
function showMsg(text, kind) {
|
|
1626
|
+
var t = document.querySelector('.wsb-toast');
|
|
1627
|
+
if (!t) {
|
|
1628
|
+
t = document.createElement('div');
|
|
1629
|
+
t.className = 'wsb-toast';
|
|
1630
|
+
document.body.appendChild(t);
|
|
1631
|
+
}
|
|
1632
|
+
t.textContent = text;
|
|
1633
|
+
t.className = 'wsb-toast show' + (kind ? ' ' + kind : '');
|
|
1634
|
+
clearTimeout(t._t);
|
|
1635
|
+
t._t = setTimeout(function () { t.className = 'wsb-toast'; }, 2600);
|
|
1636
|
+
}
|
|
1637
|
+
document.addEventListener('click', function (e) {
|
|
1638
|
+
var t = e.target && e.target.closest ? e.target.closest('[data-rename]') : null;
|
|
1639
|
+
var c = e.target && e.target.closest ? e.target.closest('[data-copypath]') : null;
|
|
1640
|
+
if (t) {
|
|
1641
|
+
e.preventDefault(); e.stopPropagation();
|
|
1642
|
+
var rel = t.getAttribute('data-rename');
|
|
1643
|
+
var name = rel.split(/[\\/]/).pop() || rel;
|
|
1644
|
+
var nn = window.prompt('重命名文件(仅改名,不跨目录)', name);
|
|
1645
|
+
if (nn == null || String(nn).trim() === '' || String(nn).trim() === name) return;
|
|
1646
|
+
fetch('/api/dsh-uploads/workspace-file/rename', {
|
|
1647
|
+
method: 'POST', headers: { 'content-type': 'application/json' },
|
|
1648
|
+
body: JSON.stringify({ path: rel, newName: String(nn).trim() })
|
|
1649
|
+
}).then(function (r) { return r.json(); }).then(function (d) {
|
|
1650
|
+
if (d && d.ok) { location.reload(); }
|
|
1651
|
+
else { showMsg('重命名失败:' + ((d && d.error) || '未知'), 'error'); }
|
|
1652
|
+
}).catch(function (err) { showMsg('重命名失败:' + err, 'error'); });
|
|
1653
|
+
} else if (c) {
|
|
1654
|
+
e.preventDefault(); e.stopPropagation();
|
|
1655
|
+
var rel2 = c.getAttribute('data-copypath');
|
|
1656
|
+
try {
|
|
1657
|
+
navigator.clipboard.writeText(rel2).then(function () { showMsg('已复制路径:' + rel2); }, function () { showMsg('复制失败', 'error'); });
|
|
1658
|
+
} catch (e2) { showMsg('复制失败', 'error'); }
|
|
1659
|
+
}
|
|
1660
|
+
});
|
|
1661
|
+
|
|
1591
1662
|
// 工作区概览(仪表盘):从各文件行的 mtime/size 统计 今日/7日/全部/总大小。
|
|
1592
1663
|
(function () {
|
|
1593
1664
|
var rows = Array.prototype.slice.call(document.querySelectorAll('tr[data-ts]'));
|
|
@@ -2121,6 +2192,12 @@ export async function apply(ctx, config = {}) {
|
|
|
2121
2192
|
handler: handlers.workspaceSave,
|
|
2122
2193
|
}), "dsh-long-plugins: workspace save route");
|
|
2123
2194
|
|
|
2195
|
+
ctx.effect(() => ctx.webServer.register({
|
|
2196
|
+
kind: "exact",
|
|
2197
|
+
path: "/api/dsh-uploads/workspace-file/rename",
|
|
2198
|
+
handler: handlers.workspaceRename,
|
|
2199
|
+
}), "dsh-long-plugins: workspace rename route");
|
|
2200
|
+
|
|
2124
2201
|
// ---- 技能文档 (skill docs) routes ----
|
|
2125
2202
|
ctx.effect(() => ctx.webServer.register({
|
|
2126
2203
|
kind: "exact",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-long-plugins",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.9",
|
|
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",
|