@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.
- package/lib/web/frontend/file-browser.js +52 -6
- package/lib/web/frontend/shadcn.html +108 -0
- package/lib/web/server.js +31 -1
- package/package.json +4 -2
|
@@ -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 =
|
|
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
|
}());
|