update html
All checks were successful
Sign Plugins / sign (push) Successful in 32s

This commit is contained in:
2026-01-03 14:25:17 +08:00
parent 29e2992987
commit 93b523b082

View File

@@ -351,375 +351,375 @@ function jsonError(status, message) {
// ============================================================================ // ============================================================================
function getIndexHTML() { function getIndexHTML() {
return '<!DOCTYPE html>\n\ return `<!DOCTYPE html>
<html lang="zh-CN">\n\ <html lang="zh-CN">
<head>\n\ <head>
<meta charset="UTF-8">\n\ <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">\n\ <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Manager</title>\n\ <title>File Manager</title>
<style>\n\ <style>
* { margin: 0; padding: 0; box-sizing: border-box; }\n\ * { margin: 0; padding: 0; box-sizing: border-box; }
:root {\n\ :root {
--bg-primary: #0f0f23;\n\ --bg-primary: #0f0f23;
--bg-secondary: #1a1a2e;\n\ --bg-secondary: #1a1a2e;
--bg-tertiary: #252542;\n\ --bg-tertiary: #252542;
--text-primary: #e4e4e7;\n\ --text-primary: #e4e4e7;
--text-secondary: #a1a1aa;\n\ --text-secondary: #a1a1aa;
--accent: #6366f1;\n\ --accent: #6366f1;
--accent-hover: #818cf8;\n\ --accent-hover: #818cf8;
--danger: #ef4444;\n\ --danger: #ef4444;
--success: #22c55e;\n\ --success: #22c55e;
--border: #2e2e4a;\n\ --border: #2e2e4a;
}\n\ }
body {\n\ body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;\n\ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: var(--bg-primary);\n\ background: var(--bg-primary);
color: var(--text-primary);\n\ color: var(--text-primary);
min-height: 100vh;\n\ min-height: 100vh;
}\n\ }
.container { max-width: 1200px; margin: 0 auto; padding: 20px; }\n\ .container { max-width: 1200px; margin: 0 auto; padding: 20px; }
header {\n\ header {
background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary));\n\ background: linear-gradient(135deg, var(--bg-secondary), var(--bg-tertiary));
padding: 20px;\n\ padding: 20px;
border-radius: 12px;\n\ border-radius: 12px;
margin-bottom: 20px;\n\ margin-bottom: 20px;
border: 1px solid var(--border);\n\ border: 1px solid var(--border);
}\n\ }
header h1 { font-size: 1.5rem; display: flex; align-items: center; gap: 10px; }\n\ header h1 { font-size: 1.5rem; display: flex; align-items: center; gap: 10px; }
header h1::before { content: "📁"; }\n\ header h1::before { content: "📁"; }
.breadcrumb {\n\ .breadcrumb {
display: flex; align-items: center; gap: 8px; margin-top: 15px;\n\ display: flex; align-items: center; gap: 8px; margin-top: 15px;
padding: 10px 15px; background: var(--bg-primary); border-radius: 8px;\n\ padding: 10px 15px; background: var(--bg-primary); border-radius: 8px;
overflow-x: auto;\n\ overflow-x: auto;
}\n\ }
.breadcrumb a {\n\ .breadcrumb a {
color: var(--accent); text-decoration: none;\n\ color: var(--accent); text-decoration: none;
white-space: nowrap;\n\ white-space: nowrap;
}\n\ }
.breadcrumb a:hover { color: var(--accent-hover); }\n\ .breadcrumb a:hover { color: var(--accent-hover); }
.breadcrumb span { color: var(--text-secondary); }\n\ .breadcrumb span { color: var(--text-secondary); }
.toolbar {\n\ .toolbar {
display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap;\n\ display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap;
}\n\ }
.btn {\n\ .btn {
padding: 10px 20px; border: none; border-radius: 8px;\n\ padding: 10px 20px; border: none; border-radius: 8px;
cursor: pointer; font-size: 14px; font-weight: 500;\n\ cursor: pointer; font-size: 14px; font-weight: 500;
transition: all 0.2s; display: flex; align-items: center; gap: 6px;\n\ transition: all 0.2s; display: flex; align-items: center; gap: 6px;
}\n\ }
.btn-primary { background: var(--accent); color: white; }\n\ .btn-primary { background: var(--accent); color: white; }
.btn-primary:hover { background: var(--accent-hover); transform: translateY(-1px); }\n\ .btn-primary:hover { background: var(--accent-hover); transform: translateY(-1px); }
.btn-danger { background: var(--danger); color: white; }\n\ .btn-danger { background: var(--danger); color: white; }
.btn-danger:hover { opacity: 0.9; }\n\ .btn-danger:hover { opacity: 0.9; }
.btn-secondary { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border); }\n\ .btn-secondary { background: var(--bg-tertiary); color: var(--text-primary); border: 1px solid var(--border); }
.btn-secondary:hover { background: var(--border); }\n\ .btn-secondary:hover { background: var(--border); }
.file-list {\n\ .file-list {
background: var(--bg-secondary); border-radius: 12px;\n\ background: var(--bg-secondary); border-radius: 12px;
border: 1px solid var(--border); overflow: hidden;\n\ border: 1px solid var(--border); overflow: hidden;
}\n\ }
.file-item {\n\ .file-item {
display: flex; align-items: center; padding: 15px 20px;\n\ display: flex; align-items: center; padding: 15px 20px;
border-bottom: 1px solid var(--border); cursor: pointer;\n\ border-bottom: 1px solid var(--border); cursor: pointer;
transition: background 0.2s;\n\ transition: background 0.2s;
}\n\ }
.file-item:last-child { border-bottom: none; }\n\ .file-item:last-child { border-bottom: none; }
.file-item:hover { background: var(--bg-tertiary); }\n\ .file-item:hover { background: var(--bg-tertiary); }
.file-item.selected { background: rgba(99, 102, 241, 0.2); }\n\ .file-item.selected { background: rgba(99, 102, 241, 0.2); }
.file-icon { font-size: 24px; margin-right: 15px; }\n\ .file-icon { font-size: 24px; margin-right: 15px; }
.file-info { flex: 1; }\n\ .file-info { flex: 1; }
.file-name { font-weight: 500; margin-bottom: 4px; }\n\ .file-name { font-weight: 500; margin-bottom: 4px; }
.file-meta { font-size: 12px; color: var(--text-secondary); }\n\ .file-meta { font-size: 12px; color: var(--text-secondary); }
.file-actions { display: flex; gap: 8px; opacity: 0; transition: opacity 0.2s; }\n\ .file-actions { display: flex; gap: 8px; opacity: 0; transition: opacity 0.2s; }
.file-item:hover .file-actions { opacity: 1; }\n\ .file-item:hover .file-actions { opacity: 1; }
.action-btn {\n\ .action-btn {
width: 32px; height: 32px; border: none; border-radius: 6px;\n\ width: 32px; height: 32px; border: none; border-radius: 6px;
cursor: pointer; display: flex; align-items: center; justify-content: center;\n\ cursor: pointer; display: flex; align-items: center; justify-content: center;
background: var(--bg-primary); color: var(--text-primary);\n\ background: var(--bg-primary); color: var(--text-primary);
transition: all 0.2s;\n\ transition: all 0.2s;
}\n\ }
.action-btn:hover { background: var(--accent); }\n\ .action-btn:hover { background: var(--accent); }
.action-btn.delete:hover { background: var(--danger); }\n\ .action-btn.delete:hover { background: var(--danger); }
.modal-overlay {\n\ .modal-overlay {
position: fixed; top: 0; left: 0; right: 0; bottom: 0;\n\ position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.7); display: flex;\n\ background: rgba(0,0,0,0.7); display: flex;
align-items: center; justify-content: center; z-index: 1000;\n\ align-items: center; justify-content: center; z-index: 1000;
}\n\ }
.modal {\n\ .modal {
background: var(--bg-secondary); border-radius: 12px;\n\ background: var(--bg-secondary); border-radius: 12px;
padding: 25px; width: 90%; max-width: 500px;\n\ padding: 25px; width: 90%; max-width: 500px;
border: 1px solid var(--border);\n\ border: 1px solid var(--border);
}\n\ }
.modal h2 { margin-bottom: 20px; font-size: 1.2rem; }\n\ .modal h2 { margin-bottom: 20px; font-size: 1.2rem; }
.modal input, .modal textarea {\n\ .modal input, .modal textarea {
width: 100%; padding: 12px; border-radius: 8px;\n\ width: 100%; padding: 12px; border-radius: 8px;
border: 1px solid var(--border); background: var(--bg-primary);\n\ border: 1px solid var(--border); background: var(--bg-primary);
color: var(--text-primary); margin-bottom: 15px; font-size: 14px;\n\ color: var(--text-primary); margin-bottom: 15px; font-size: 14px;
}\n\ }
.modal textarea { min-height: 200px; resize: vertical; font-family: monospace; }\n\ .modal textarea { min-height: 200px; resize: vertical; font-family: monospace; }
.modal-actions { display: flex; gap: 10px; justify-content: flex-end; }\n\ .modal-actions { display: flex; gap: 10px; justify-content: flex-end; }
.empty-state {\n\ .empty-state {
text-align: center; padding: 60px 20px; color: var(--text-secondary);\n\ text-align: center; padding: 60px 20px; color: var(--text-secondary);
}\n\ }
.empty-state::before { content: "📂"; font-size: 48px; display: block; margin-bottom: 15px; }\n\ .empty-state::before { content: "📂"; font-size: 48px; display: block; margin-bottom: 15px; }
.loading { text-align: center; padding: 40px; color: var(--text-secondary); }\n\ .loading { text-align: center; padding: 40px; color: var(--text-secondary); }
.toast {\n\ .toast {
position: fixed; bottom: 20px; right: 20px; padding: 15px 25px;\n\ position: fixed; bottom: 20px; right: 20px; padding: 15px 25px;
background: var(--success); color: white; border-radius: 8px;\n\ background: var(--success); color: white; border-radius: 8px;
animation: slideIn 0.3s ease;\n\ animation: slideIn 0.3s ease;
}\n\ }
.toast.error { background: var(--danger); }\n\ .toast.error { background: var(--danger); }
@keyframes slideIn { from { transform: translateX(100%); opacity: 0; } }\n\ @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } }
</style>\n\ </style>
</head>\n\ </head>
<body>\n\ <body>
<div class="container">\n\ <div class="container">
<header>\n\ <header>
<h1>File Manager</h1>\n\ <h1>File Manager</h1>
<div class="breadcrumb" id="breadcrumb"></div>\n\ <div class="breadcrumb" id="breadcrumb"></div>
</header>\n\ </header>
<div class="toolbar">\n\ <div class="toolbar">
<button class="btn btn-primary" onclick="showNewFolderModal()">📁 新建文件夹</button>\n\ <button class="btn btn-primary" onclick="showNewFolderModal()">📁 新建文件夹</button>
<button class="btn btn-primary" onclick="showNewFileModal()">📄 新建文件</button>\n\ <button class="btn btn-primary" onclick="showNewFileModal()">📄 新建文件</button>
<button class="btn btn-secondary" onclick="refresh()">🔄 刷新</button>\n\ <button class="btn btn-secondary" onclick="refresh()">🔄 刷新</button>
</div>\n\ </div>
<div class="file-list" id="fileList">\n\ <div class="file-list" id="fileList">
<div class="loading">加载中...</div>\n\ <div class="loading">加载中...</div>
</div>\n\ </div>
</div>\n\ </div>
<div id="modalContainer"></div>\n\ <div id="modalContainer"></div>
<script>\n\ <script>
var currentPath = "/";\n\ var currentPath = "/";
\n\
function init() { loadDirectory("/"); }\n\ function init() { loadDirectory("/"); }
\n\
function loadDirectory(path) {\n\ function loadDirectory(path) {
currentPath = path;\n\ currentPath = path;
updateBreadcrumb();\n\ updateBreadcrumb();
document.getElementById("fileList").innerHTML = "<div class=\\"loading\\">加载中...</div>";\n\ document.getElementById("fileList").innerHTML = "<div class=\\"loading\\">加载中...</div>";
\n\
fetch("/api/list?path=" + encodeURIComponent(path))\n\ fetch("/api/list?path=" + encodeURIComponent(path))
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (!data.success) { showToast(data.error, true); return; }\n\ if (!data.success) { showToast(data.error, true); return; }
renderFileList(data.data.entries);\n\ renderFileList(data.data.entries);
})\n\ })
.catch(function(e) { showToast("加载失败: " + e.message, true); });\n\ .catch(function(e) { showToast("加载失败: " + e.message, true); });
}\n\ }
\n\
function renderFileList(entries) {\n\ function renderFileList(entries) {
var list = document.getElementById("fileList");\n\ var list = document.getElementById("fileList");
if (!entries || entries.length === 0) {\n\ if (!entries || entries.length === 0) {
list.innerHTML = "<div class=\\"empty-state\\">空文件夹</div>";\n\ list.innerHTML = "<div class=\\"empty-state\\">空文件夹</div>";
return;\n\ return;
}\n\ }
\n\
var html = "";\n\ var html = "";
if (currentPath !== "/") {\n\ if (currentPath !== "/") {
html += "<div class=\\"file-item\\" onclick=\\"goUp()\\"><span class=\\"file-icon\\">⬆️</span><div class=\\"file-info\\"><div class=\\"file-name\\">..</div><div class=\\"file-meta\\">返回上级</div></div></div>";\n\ html += "<div class=\\"file-item\\" onclick=\\"goUp()\\"><span class=\\"file-icon\\">⬆️</span><div class=\\"file-info\\"><div class=\\"file-name\\">..</div><div class=\\"file-meta\\">返回上级</div></div></div>";
}\n\ }
\n\
for (var i = 0; i < entries.length; i++) {\n\ for (var i = 0; i < entries.length; i++) {
var e = entries[i];\n\ var e = entries[i];
var icon = e.isDir ? "📁" : getFileIcon(e.name);\n\ var icon = e.isDir ? "📁" : getFileIcon(e.name);
var size = e.isDir ? "" : formatSize(e.size);\n\ var size = e.isDir ? "" : formatSize(e.size);
var onclick = e.isDir ? "navigateTo(\\"" + e.name + "\\")" : "viewFile(\\"" + e.name + "\\")";\n\ var onclick = e.isDir ? "navigateTo(\\"" + e.name + "\\")" : "viewFile(\\"" + e.name + "\\")";
\n\
html += "<div class=\\"file-item\\" onclick=\\""+ onclick +"\\">";\n\ html += "<div class=\\"file-item\\" onclick=\\""+ onclick +"\\">";
html += "<span class=\\"file-icon\\">" + icon + "</span>";\n\ html += "<span class=\\"file-icon\\">" + icon + "</span>";
html += "<div class=\\"file-info\\"><div class=\\"file-name\\">" + escapeHtml(e.name) + "</div>";\n\ html += "<div class=\\"file-info\\"><div class=\\"file-name\\">" + escapeHtml(e.name) + "</div>";
html += "<div class=\\"file-meta\\">" + size + "</div></div>";\n\ html += "<div class=\\"file-meta\\">" + size + "</div></div>";
html += "<div class=\\"file-actions\\">";\n\ html += "<div class=\\"file-actions\\">";
if (!e.isDir) {\n\ if (!e.isDir) {
html += "<button class=\\"action-btn\\" onclick=\\"event.stopPropagation();downloadFile(\\'"+e.name+"\\')\\">⬇️</button>";\n\ html += "<button class=\\"action-btn\\" onclick=\\"event.stopPropagation();downloadFile('" + e.name + "')\\">⬇️</button>";
}\n\ }
html += "<button class=\\"action-btn\\" onclick=\\"event.stopPropagation();renameItem(\\'"+e.name+"\\')\\">✏️</button>";\n\ html += "<button class=\\"action-btn\\" onclick=\\"event.stopPropagation();renameItem('" + e.name + "')\\">✏️</button>";
html += "<button class=\\"action-btn delete\\" onclick=\\"event.stopPropagation();deleteItem(\\'"+e.name+"\\')\\">🗑️</button>";\n\ html += "<button class=\\"action-btn delete\\" onclick=\\"event.stopPropagation();deleteItem('" + e.name + "')\\">🗑️</button>";
html += "</div></div>";\n\ html += "</div></div>";
}\n\ }
list.innerHTML = html;\n\ list.innerHTML = html;
}\n\ }
\n\
function updateBreadcrumb() {\n\ function updateBreadcrumb() {
var parts = currentPath.split("/").filter(function(p) { return p; });\n\ var parts = currentPath.split("/").filter(function(p) { return p; });
var html = "<a href=\\"#\\" onclick=\\"loadDirectory(\\'/\\');return false;\\">🏠 根目录</a>";\n\ var html = "<a href=\\"#\\" onclick=\\"loadDirectory('/');return false;\\">🏠 根目录</a>";
var path = "";\n\ var path = "";
for (var i = 0; i < parts.length; i++) {\n\ for (var i = 0; i < parts.length; i++) {
path += "/" + parts[i];\n\ path += "/" + parts[i];
html += "<span>/</span><a href=\\"#\\" onclick=\\"loadDirectory(\\'"+path+"\\');return false;\\">" + escapeHtml(parts[i]) + "</a>";\n\ html += "<span>/</span><a href=\\"#\\" onclick=\\"loadDirectory('"+path+"');return false;\\">" + escapeHtml(parts[i]) + "</a>";
}\n\ }
document.getElementById("breadcrumb").innerHTML = html;\n\ document.getElementById("breadcrumb").innerHTML = html;
}\n\ }
\n\
function navigateTo(name) { loadDirectory(joinPath(currentPath, name)); }\n\ function navigateTo(name) { loadDirectory(joinPath(currentPath, name)); }
function goUp() {\n\ function goUp() {
var parts = currentPath.split("/").filter(function(p) { return p; });\n\ var parts = currentPath.split("/").filter(function(p) { return p; });
parts.pop();\n\ parts.pop();
loadDirectory("/" + parts.join("/"));\n\ loadDirectory("/" + parts.join("/"));
}\n\ }
function refresh() { loadDirectory(currentPath); }\n\ function refresh() { loadDirectory(currentPath); }
\n\
function joinPath(base, name) {\n\ function joinPath(base, name) {
if (base === "/") return "/" + name;\n\ if (base === "/") return "/" + name;
return base + "/" + name;\n\ return base + "/" + name;
}\n\ }
\n\
function viewFile(name) {\n\ function viewFile(name) {
var path = joinPath(currentPath, name);\n\ var path = joinPath(currentPath, name);
fetch("/api/read", {\n\ fetch("/api/read", {
method: "POST",\n\ method: "POST",
headers: {"Content-Type": "application/json"},\n\ headers: {"Content-Type": "application/json"},
body: JSON.stringify({path: path})\n\ body: JSON.stringify({path: path})
})\n\ })
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (!data.success) { showToast(data.error, true); return; }\n\ if (!data.success) { showToast(data.error, true); return; }
showEditModal(name, data.data.content);\n\ showEditModal(name, data.data.content);
});\n\ });
}\n\ }
\n\
function downloadFile(name) {\n\ function downloadFile(name) {
var path = joinPath(currentPath, name);\n\ var path = joinPath(currentPath, name);
window.open("/api/download?path=" + encodeURIComponent(path));\n\ window.open("/api/download?path=" + encodeURIComponent(path));
}\n\ }
\n\
function deleteItem(name) {\n\ function deleteItem(name) {
if (!confirm("确定删除 " + name + "")) return;\n\ if (!confirm("确定删除 " + name + "")) return;
var path = joinPath(currentPath, name);\n\ var path = joinPath(currentPath, name);
fetch("/api/delete", {\n\ fetch("/api/delete", {
method: "POST",\n\ method: "POST",
headers: {"Content-Type": "application/json"},\n\ headers: {"Content-Type": "application/json"},
body: JSON.stringify({path: path})\n\ body: JSON.stringify({path: path})
})\n\ })
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (data.success) { showToast("已删除"); refresh(); }\n\ if (data.success) { showToast("已删除"); refresh(); }
else { showToast(data.error, true); }\n\ else { showToast(data.error, true); }
});\n\ });
}\n\ }
\n\
function renameItem(name) {\n\ function renameItem(name) {
var newName = prompt("输入新名称:", name);\n\ var newName = prompt("输入新名称:", name);
if (!newName || newName === name) return;\n\ if (!newName || newName === name) return;
var oldPath = joinPath(currentPath, name);\n\ var oldPath = joinPath(currentPath, name);
var newPath = joinPath(currentPath, newName);\n\ var newPath = joinPath(currentPath, newName);
fetch("/api/rename", {\n\ fetch("/api/rename", {
method: "POST",\n\ method: "POST",
headers: {"Content-Type": "application/json"},\n\ headers: {"Content-Type": "application/json"},
body: JSON.stringify({path: oldPath, newPath: newPath})\n\ body: JSON.stringify({path: oldPath, newPath: newPath})
})\n\ })
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (data.success) { showToast("已重命名"); refresh(); }\n\ if (data.success) { showToast("已重命名"); refresh(); }
else { showToast(data.error, true); }\n\ else { showToast(data.error, true); }
});\n\ });
}\n\ }
\n\
function showNewFolderModal() {\n\ function showNewFolderModal() {
var name = prompt("文件夹名称:");\n\ var name = prompt("文件夹名称:");
if (!name) return;\n\ if (!name) return;
fetch("/api/mkdir", {\n\ fetch("/api/mkdir", {
method: "POST",\n\ method: "POST",
headers: {"Content-Type": "application/json"},\n\ headers: {"Content-Type": "application/json"},
body: JSON.stringify({path: joinPath(currentPath, name)})\n\ body: JSON.stringify({path: joinPath(currentPath, name)})
})\n\ })
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (data.success) { showToast("已创建"); refresh(); }\n\ if (data.success) { showToast("已创建"); refresh(); }
else { showToast(data.error, true); }\n\ else { showToast(data.error, true); }
});\n\ });
}\n\ }
\n\
function showNewFileModal() {\n\ function showNewFileModal() {
var html = "<div class=\\"modal-overlay\\" onclick=\\"closeModal()\\">";\n\ var html = "<div class=\\"modal-overlay\\" onclick=\\"closeModal()\\">";
html += "<div class=\\"modal\\" onclick=\\"event.stopPropagation()\\">";\n\ html += "<div class=\\"modal\\" onclick=\\"event.stopPropagation()\\">";
html += "<h2>📄 新建文件</h2>";\n\ html += "<h2>📄 新建文件</h2>";
html += "<input type=\\"text\\" id=\\"newFileName\\" placeholder=\\"文件名\\">";\n\ html += "<input type=\\"text\\" id=\\"newFileName\\" placeholder=\\"文件名\\">";
html += "<textarea id=\\"newFileContent\\" placeholder=\\"文件内容\\"></textarea>";\n\ html += "<textarea id=\\"newFileContent\\" placeholder=\\"文件内容\\"></textarea>";
html += "<div class=\\"modal-actions\\">";\n\ html += "<div class=\\"modal-actions\\">";
html += "<button class=\\"btn btn-secondary\\" onclick=\\"closeModal()\\">取消</button>";\n\ html += "<button class=\\"btn btn-secondary\\" onclick=\\"closeModal()\\">取消</button>";
html += "<button class=\\"btn btn-primary\\" onclick=\\"createFile()\\">创建</button>";\n\ html += "<button class=\\"btn btn-primary\\" onclick=\\"createFile()\\">创建</button>";
html += "</div></div></div>";\n\ html += "</div></div></div>";
document.getElementById("modalContainer").innerHTML = html;\n\ document.getElementById("modalContainer").innerHTML = html;
}\n\ }
\n\
function showEditModal(name, content) {\n\ function showEditModal(name, content) {
var html = "<div class=\\"modal-overlay\\" onclick=\\"closeModal()\\">";\n\ var html = "<div class=\\"modal-overlay\\" onclick=\\"closeModal()\\">";
html += "<div class=\\"modal\\" onclick=\\"event.stopPropagation()\\">";\n\ html += "<div class=\\"modal\\" onclick=\\"event.stopPropagation()\\">";
html += "<h2>📝 编辑: " + escapeHtml(name) + "</h2>";\n\ html += "<h2>📝 编辑: " + escapeHtml(name) + "</h2>";
html += "<input type=\\"hidden\\" id=\\"editFileName\\" value=\\"" + escapeHtml(name) + "\\">";\n\ html += "<input type=\\"hidden\\" id=\\"editFileName\\" value=\\"" + escapeHtml(name) + "\\">";
html += "<textarea id=\\"editFileContent\\">" + escapeHtml(content) + "</textarea>";\n\ html += "<textarea id=\\"editFileContent\\">" + escapeHtml(content) + "</textarea>";
html += "<div class=\\"modal-actions\\">";\n\ html += "<div class=\\"modal-actions\\">";
html += "<button class=\\"btn btn-secondary\\" onclick=\\"closeModal()\\">取消</button>";\n\ html += "<button class=\\"btn btn-secondary\\" onclick=\\"closeModal()\\">取消</button>";
html += "<button class=\\"btn btn-primary\\" onclick=\\"saveFile()\\">保存</button>";\n\ html += "<button class=\\"btn btn-primary\\" onclick=\\"saveFile()\\">保存</button>";
html += "</div></div></div>";\n\ html += "</div></div></div>";
document.getElementById("modalContainer").innerHTML = html;\n\ document.getElementById("modalContainer").innerHTML = html;
}\n\ }
\n\
function createFile() {\n\ function createFile() {
var name = document.getElementById("newFileName").value;\n\ var name = document.getElementById("newFileName").value;
var content = document.getElementById("newFileContent").value;\n\ var content = document.getElementById("newFileContent").value;
if (!name) { showToast("请输入文件名", true); return; }\n\ if (!name) { showToast("请输入文件名", true); return; }
fetch("/api/write", {\n\ fetch("/api/write", {
method: "POST",\n\ method: "POST",
headers: {"Content-Type": "application/json"},\n\ headers: {"Content-Type": "application/json"},
body: JSON.stringify({path: joinPath(currentPath, name), content: content})\n\ body: JSON.stringify({path: joinPath(currentPath, name), content: content})
})\n\ })
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (data.success) { closeModal(); showToast("已创建"); refresh(); }\n\ if (data.success) { closeModal(); showToast("已创建"); refresh(); }
else { showToast(data.error, true); }\n\ else { showToast(data.error, true); }
});\n\ });
}\n\ }
\n\
function saveFile() {\n\ function saveFile() {
var name = document.getElementById("editFileName").value;\n\ var name = document.getElementById("editFileName").value;
var content = document.getElementById("editFileContent").value;\n\ var content = document.getElementById("editFileContent").value;
fetch("/api/write", {\n\ fetch("/api/write", {
method: "POST",\n\ method: "POST",
headers: {"Content-Type": "application/json"},\n\ headers: {"Content-Type": "application/json"},
body: JSON.stringify({path: joinPath(currentPath, name), content: content})\n\ body: JSON.stringify({path: joinPath(currentPath, name), content: content})
})\n\ })
.then(function(r) { return r.json(); })\n\ .then(function(r) { return r.json(); })
.then(function(data) {\n\ .then(function(data) {
if (data.success) { closeModal(); showToast("已保存"); }\n\ if (data.success) { closeModal(); showToast("已保存"); }
else { showToast(data.error, true); }\n\ else { showToast(data.error, true); }
});\n\ });
}\n\ }
\n\
function closeModal() { document.getElementById("modalContainer").innerHTML = ""; }\n\ function closeModal() { document.getElementById("modalContainer").innerHTML = ""; }
\n\
function showToast(msg, isError) {\n\ function showToast(msg, isError) {
var toast = document.createElement("div");\n\ var toast = document.createElement("div");
toast.className = "toast" + (isError ? " error" : "");\n\ toast.className = "toast" + (isError ? " error" : "");
toast.textContent = msg;\n\ toast.textContent = msg;
document.body.appendChild(toast);\n\ document.body.appendChild(toast);
setTimeout(function() { toast.remove(); }, 3000);\n\ setTimeout(function() { toast.remove(); }, 3000);
}\n\ }
\n\
function formatSize(bytes) {\n\ function formatSize(bytes) {
if (bytes === 0) return "0 B";\n\ if (bytes === 0) return "0 B";
var k = 1024;\n\ var k = 1024;
var sizes = ["B", "KB", "MB", "GB"];\n\ var sizes = ["B", "KB", "MB", "GB"];
var i = Math.floor(Math.log(bytes) / Math.log(k));\n\ var i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];\n\ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i];
}\n\ }
\n\
function getFileIcon(name) {\n\ function getFileIcon(name) {
var ext = name.split(".").pop().toLowerCase();\n\ var ext = name.split(".").pop().toLowerCase();
var icons = {\n\ var icons = {
js: "📜", json: "📋", html: "🌐", css: "🎨", md: "📝",\n\ js: "📜", json: "📋", html: "🌐", css: "🎨", md: "📝",
txt: "📄", log: "📃", py: "🐍", go: "🔵", java: "☕",\n\ txt: "📄", log: "📃", py: "🐍", go: "🔵", java: "☕",
png: "🖼️", jpg: "🖼️", gif: "🖼️", svg: "🖼️",\n\ png: "🖼️", jpg: "🖼️", gif: "🖼️", svg: "🖼️",
mp3: "🎵", mp4: "🎬", pdf: "📕", zip: "📦", tar: "📦"\n\ mp3: "🎵", mp4: "🎬", pdf: "📕", zip: "📦", tar: "📦"
};\n\ };
return icons[ext] || "📄";\n\ return icons[ext] || "📄";
}\n\ }
\n\
function escapeHtml(str) {\n\ function escapeHtml(str) {
return String(str).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");\n\ return String(str).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
}\n\ }
\n\
init();\n\ init();
</script>\n\ </script>
</body>\n\ </body>
</html>'; </html>`;
} }