@xcanwin/manyoyo 6.2.17 → 7.0.1

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.
@@ -9,6 +9,18 @@
9
9
  .replace(/'/g, ''');
10
10
  }
11
11
 
12
+ // 双向控制符(RTLO 等)/ 零宽字符可以让文件名视觉上和真实字节完全不一致
13
+ // (例如 "cod" + U+202E + "exe.txt" 会被浏览器渲染成 "codtxt.exe"),
14
+ // 展示前统一替换成可见的 \uXXXX 记法,破坏其排版效果并让人一眼看出异常。
15
+ // 范围:U+200B-U+200F(零宽字符/LRM/RLM)、U+202A-U+202E(嵌入/覆盖方向控制符)、
16
+ // U+2060-U+2069(零宽连接符/方向隔离符)、U+FEFF(BOM/零宽不换行空格)、U+061C(阿拉伯字母标记)。
17
+ const SUSPICIOUS_UNICODE_PATTERN = /[\u200B-\u200F\u202A-\u202E\u2060-\u2069\uFEFF\u061C]/g;
18
+ function sanitizeDisplayText(value) {
19
+ return String(value == null ? '' : value).replace(SUSPICIOUS_UNICODE_PATTERN, function (ch) {
20
+ return '\\u' + ch.codePointAt(0).toString(16).toUpperCase().padStart(4, '0');
21
+ });
22
+ }
23
+
12
24
  function formatBytes(size) {
13
25
  const value = Number(size || 0);
14
26
  if (!Number.isFinite(value) || value <= 0) {
@@ -46,6 +58,8 @@
46
58
  const parts = [];
47
59
  if (entry && entry.kind === 'directory') {
48
60
  parts.push('目录');
61
+ } else if (entry && entry.kind === 'symlink') {
62
+ parts.push('符号链接');
49
63
  } else {
50
64
  parts.push(formatBytes(entry && entry.size));
51
65
  }
@@ -55,6 +69,16 @@
55
69
  return parts.join(' · ');
56
70
  }
57
71
 
72
+ function buildEntryTitle(entry) {
73
+ const basePath = sanitizeDisplayText(entry && (entry.path || entry.name) || '');
74
+ if (entry && entry.kind === 'symlink') {
75
+ return entry.symlinkTarget
76
+ ? `${basePath} → ${sanitizeDisplayText(entry.symlinkTarget)}`
77
+ : `${basePath}(符号链接目标无法解析,可能已损坏)`;
78
+ }
79
+ return basePath;
80
+ }
81
+
58
82
  function inferLanguageFromPath(filePath) {
59
83
  const text = String(filePath || '').toLowerCase();
60
84
  if (text.endsWith('.md') || text.endsWith('.markdown')) return 'markdown';
@@ -237,7 +261,7 @@
237
261
  state.previewReadOnly = true;
238
262
  state.previewDirty = false;
239
263
  if (previewTitleNode) {
240
- previewTitleNode.textContent = title;
264
+ previewTitleNode.textContent = sanitizeDisplayText(title);
241
265
  }
242
266
  if (previewMetaNode) {
243
267
  previewMetaNode.textContent = description;
@@ -282,7 +306,7 @@
282
306
  return;
283
307
  }
284
308
  if (previewTitleNode) {
285
- previewTitleNode.textContent = payload.path || '未命名文件';
309
+ previewTitleNode.textContent = sanitizeDisplayText(payload.path) || '未命名文件';
286
310
  }
287
311
  updatePreviewMeta();
288
312
  if (!previewBodyNode) {
@@ -366,6 +390,23 @@
366
390
  syncSaveButton();
367
391
  }
368
392
 
393
+ function openSymlinkEntry(entry) {
394
+ const safeName = sanitizeDisplayText(entry.name);
395
+ const message = entry.symlinkTarget
396
+ ? `"${safeName}" 是符号链接,实际指向:\n${sanitizeDisplayText(entry.symlinkTarget)}\n\n是否继续访问?`
397
+ : `"${safeName}" 是一个无法解析的符号链接(可能已损坏),是否仍要尝试访问?`;
398
+ confirmFn(message).then(function (proceed) {
399
+ if (!proceed) {
400
+ return;
401
+ }
402
+ if (entry.symlinkTargetKind === 'directory') {
403
+ loadDirectory(entry.path);
404
+ return;
405
+ }
406
+ loadFile(entry.path, entry);
407
+ });
408
+ }
409
+
369
410
  function renderList() {
370
411
  if (pathNode) {
371
412
  pathNode.value = state.pathDraft || state.currentPath || state.containerPath || '/';
@@ -408,7 +449,7 @@
408
449
  const parentButton = document.createElement('button');
409
450
  parentButton.type = 'button';
410
451
  parentButton.className = 'files-entry files-entry-parent';
411
- parentButton.title = state.parentPath;
452
+ parentButton.title = sanitizeDisplayText(state.parentPath);
412
453
  parentButton.addEventListener('click', function () {
413
454
  loadDirectory(state.parentPath);
414
455
  });
@@ -430,8 +471,12 @@
430
471
  const button = document.createElement('button');
431
472
  button.type = 'button';
432
473
  button.className = 'files-entry' + (state.selectedPath === entry.path ? ' is-active' : '');
433
- button.title = String(entry.path || entry.name || '');
474
+ button.title = buildEntryTitle(entry);
434
475
  button.addEventListener('click', function () {
476
+ if (entry.kind === 'symlink') {
477
+ openSymlinkEntry(entry);
478
+ return;
479
+ }
435
480
  if (entry.kind === 'directory') {
436
481
  loadDirectory(entry.path);
437
482
  return;
@@ -440,7 +485,7 @@
440
485
  });
441
486
  button.innerHTML = `
442
487
  <span class="files-entry-name">
443
- <span class="files-entry-title">${escapeHtml(entry.name || entry.path || '未命名')}</span>
488
+ <span class="files-entry-title">${escapeHtml(sanitizeDisplayText(entry.name || entry.path || '未命名'))}</span>
444
489
  </span>
445
490
  <span class="files-entry-meta">${escapeHtml(buildEntryMeta(entry))}</span>
446
491
  `;
@@ -749,6 +794,7 @@
749
794
  }
750
795
 
751
796
  window.ManyoyoFileBrowser = {
752
- create
797
+ create,
798
+ sanitizeDisplayText
753
799
  };
754
800
  }());