local-cmd-runner 1.0.4 → 1.0.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "local-cmd-runner",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "Run local shell commands from a web page",
5
5
  "homepage": "https://github.com/codewoow/local_cmd#readme",
6
6
  "bugs": {
package/public/app.js CHANGED
@@ -206,6 +206,14 @@ function renderTree(items, relPath) {
206
206
 
207
207
  if (item.isDirectory) {
208
208
  el.onclick = () => loadTree(item.path);
209
+
210
+ const uploadDirBtn = document.createElement('button');
211
+ uploadDirBtn.innerHTML = '上传到此';
212
+ uploadDirBtn.onclick = (e) => {
213
+ e.stopPropagation();
214
+ triggerDirectoryUpload(item.path);
215
+ };
216
+ actions.appendChild(uploadDirBtn);
209
217
  } else {
210
218
  const previewBtn = document.createElement('button');
211
219
  previewBtn.innerHTML = '查看';
@@ -226,15 +234,21 @@ function renderTree(items, relPath) {
226
234
  });
227
235
  }
228
236
 
237
+ let pendingUploadPath = '';
238
+ function triggerDirectoryUpload(targetPath) {
239
+ pendingUploadPath = targetPath;
240
+ fmUploadInput.click();
241
+ }
242
+
229
243
  if (fmRefreshBtn) {
230
244
  fmRefreshBtn.onclick = () => loadTree(currentRelPath);
231
- fmUploadBtn.onclick = () => fmUploadInput.click();
245
+ fmUploadBtn.onclick = () => triggerDirectoryUpload(currentRelPath);
232
246
  fmUploadInput.onchange = async (e) => {
233
247
  const file = e.target.files[0];
234
248
  if (!file) return;
235
249
  const formData = new FormData();
236
250
  formData.append('file', file);
237
- formData.append('path', currentRelPath);
251
+ formData.append('path', pendingUploadPath);
238
252
 
239
253
  try {
240
254
  fmUploadBtn.textContent = '...';
@@ -255,14 +269,43 @@ if (fmRefreshBtn) {
255
269
 
256
270
  async function previewFile(path) {
257
271
  try {
258
- const res = await fetch(`/cmd/api/preview?path=${encodeURIComponent(path)}`);
259
- if (!res.ok) throw new Error('获取预览失败');
260
- const text = await res.text();
261
272
  previewTitle.textContent = path;
262
- previewBody.textContent = text;
263
273
  previewModal.classList.remove('hidden');
274
+ previewBody.innerHTML = '<div style="margin: 20px;">加载中...</div>';
275
+
276
+ const ext = path.split('.').pop().toLowerCase();
277
+ const isImage = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'ico'].includes(ext);
278
+ const isHtml = ['html', 'htm'].includes(ext);
279
+ const isVideo = ['mp4', 'webm', 'ogg'].includes(ext);
280
+ const isAudio = ['mp3', 'wav', 'ogg'].includes(ext);
281
+
282
+ const fileUrl = `/cmd/api/preview?path=${encodeURIComponent(path)}`;
283
+
284
+ if (isImage) {
285
+ previewBody.innerHTML = `<img src="${fileUrl}" style="max-width: 100%; max-height: 70vh; object-fit: contain; border-radius: 4px;" />`;
286
+ } else if (isVideo) {
287
+ previewBody.innerHTML = `<video src="${fileUrl}" controls style="max-width: 100%; max-height: 70vh; border-radius: 4px;"></video>`;
288
+ } else if (isAudio) {
289
+ previewBody.innerHTML = `<audio src="${fileUrl}" controls style="width: 100%; margin-top: 20px;"></audio>`;
290
+ } else if (isHtml) {
291
+ previewBody.innerHTML = `<iframe src="${fileUrl}" style="width: 100%; min-height: 60vh; border: none; background: white; border-radius: 4px;"></iframe>`;
292
+ } else {
293
+ const res = await fetch(fileUrl);
294
+ if (!res.ok) throw new Error('获取预览失败');
295
+ const text = await res.text();
296
+ previewBody.innerHTML = '';
297
+ const pre = document.createElement('pre');
298
+ pre.textContent = text;
299
+ pre.style.whiteSpace = 'pre-wrap';
300
+ pre.style.wordBreak = 'break-all';
301
+ pre.style.margin = '0';
302
+ pre.style.fontFamily = "'Fira Code', monospace";
303
+ pre.style.width = "100%";
304
+ previewBody.appendChild(pre);
305
+ }
264
306
  } catch (err) {
265
307
  alert('预览失败: ' + err.message);
308
+ previewModal.classList.add('hidden');
266
309
  }
267
310
  }
268
311
 
package/public/index.html CHANGED
@@ -68,7 +68,7 @@
68
68
  <div id="previewModal" class="modal-overlay hidden">
69
69
  <div class="modal-content preview-modal-content">
70
70
  <h3 id="previewTitle" style="word-break: break-all;">文件预览</h3>
71
- <pre class="preview-body" id="previewBody"></pre>
71
+ <div class="preview-body" id="previewBody" style="display: flex; flex-direction: column; align-items: center; justify-content: center;"></div>
72
72
  <div class="modal-actions">
73
73
  <button id="previewClose" class="btn-primary">关闭</button>
74
74
  </div>
package/server.js CHANGED
@@ -64,8 +64,7 @@ app.get('/cmd/api/tree', async (req, res) => {
64
64
  app.get('/cmd/api/preview', async (req, res) => {
65
65
  try {
66
66
  const targetPath = getSecurePath(req.query.path);
67
- const content = await fs.promises.readFile(targetPath, 'utf8');
68
- res.send(content);
67
+ res.sendFile(targetPath);
69
68
  } catch (err) {
70
69
  res.status(500).send(err.message);
71
70
  }