(function() { 'use strict'; const API_BASE = '/api'; let currentPath = '/'; let selectedFiles = new Set(); let eventSource = null; const elements = { fileListBody: document.getElementById('file-list-body'), breadcrumb: document.getElementById('breadcrumb'), pathDisplay: document.getElementById('path-display'), connectionStatus: document.getElementById('connection-status'), btnUpload: document.getElementById('btn-upload'), btnDownload: document.getElementById('btn-download'), btnDelete: document.getElementById('btn-delete'), btnMove: document.getElementById('btn-move'), btnRefresh: document.getElementById('btn-refresh'), btnNewDir: document.getElementById('btn-new-dir'), selectAll: document.getElementById('select-all'), fileInput: document.getElementById('file-input'), dropZone: document.getElementById('drop-zone'), previewModal: document.getElementById('preview-modal'), previewTitle: document.getElementById('preview-title'), previewBody: document.getElementById('preview-body'), previewClose: document.getElementById('preview-close'), moveModal: document.getElementById('move-modal'), moveClose: document.getElementById('move-close'), moveConfirm: document.getElementById('move-confirm'), moveCancel: document.getElementById('move-cancel'), moveFilename: document.getElementById('move-filename'), moveBreadcrumb: document.getElementById('move-breadcrumb'), dirTree: document.getElementById('dir-tree'), moveSource: document.getElementById('move-source'), moveDest: document.getElementById('move-dest'), newDirModal: document.getElementById('new-dir-modal'), newDirClose: document.getElementById('new-dir-close'), newDirConfirm: document.getElementById('new-dir-confirm'), newDirCancel: document.getElementById('new-dir-cancel'), newDirName: document.getElementById('new-dir-name'), renameModal: document.getElementById('rename-modal'), renameClose: document.getElementById('rename-close'), renameConfirm: document.getElementById('rename-confirm'), renameCancel: document.getElementById('rename-cancel'), renameName: document.getElementById('rename-name'), renamePath: document.getElementById('rename-path'), uploadModal: document.getElementById('upload-modal'), uploadClose: document.getElementById('upload-close'), uploadCancel: document.getElementById('upload-cancel'), uploadConfirm: document.getElementById('upload-confirm'), uploadList: document.getElementById('upload-list'), uploadProgressContainer: document.getElementById('upload-progress-container'), uploadProgressBar: document.getElementById('upload-progress-bar'), uploadStats: document.getElementById('upload-stats'), searchInput: document.getElementById('search-input'), searchBtn: document.getElementById('search-btn'), notification: document.getElementById('notification') }; let uploadXhrs = []; let isUploading = false; let searchResults = []; function init() { loadFiles(currentPath); setupEventListeners(); startWatch(); } function loadFiles(path) { elements.fileListBody.innerHTML = '加载中...'; fetch(`${API_BASE}/files?path=${encodeURIComponent(path)}`) .then(response => response.json()) .then(data => { renderFileList(data.files); updateBreadcrumb(path); elements.pathDisplay.textContent = path || '/'; }) .catch(error => { showNotification('加载失败: ' + error.message, 'error'); elements.fileListBody.innerHTML = '加载失败'; }); } function renderFileList(files) { if (!files || files.length === 0) { elements.fileListBody.innerHTML = '📂此目录为空'; return; } const dirs = files.filter(f => f.isDir).sort((a, b) => a.name.localeCompare(b.name)); const regularFiles = files.filter(f => !f.isDir).sort((a, b) => a.name.localeCompare(b.name)); const sortedFiles = [...dirs, ...regularFiles]; elements.fileListBody.innerHTML = sortedFiles.map(file => ` ${getFileIcon(file.name, file.isDir)} ${escapeHtml(file.name)} ${file.isDir ? '-' : formatFileSize(file.size)} ${formatTime(file.modTime)}
${file.isDir ? '' : ``} ${file.canPreview ? `` : ''}
`).join(''); updateButtonStates(); } function getFileIcon(name, isDir) { if (isDir) return '📁'; const ext = name.split('.').pop().toLowerCase(); const icons = { pdf: '📕', doc: '📘', docx: '📘', xls: '📗', xlsx: '📗', ppt: '📙', pptx: '📙', txt: '📄', md: '📝', html: '🌐', htm: '🌐', css: '🎨', js: '📜', json: '{ }', xml: '📋', png: '🖼️', jpg: '🖼️', jpeg: '🖼️', gif: '🖼️', bmp: '🖼️', webp: '🖼️', mp3: '🎵', wav: '🎵', ogg: '🎵', flac: '🎵', mp4: '🎬', avi: '🎬', mkv: '🎬', mov: '🎬', webm: '🎬', zip: '📦', rar: '📦', '7z': '📦', tar: '📦', gz: '📦', exe: '⚙️', app: '⚙️', sh: '💻', go: '🔷', py: '🐍', java: '☕', c: '🔵', cpp: '🔵', h: '🔵', sql: '🗃️', csv: '📊', svg: '🎯' }; return icons[ext] || '📄'; } function formatFileSize(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } function formatTime(timeStr) { const date = new Date(timeStr); const now = new Date(); const isToday = date.toDateString() === now.toDateString(); if (isToday) { return '今天 ' + date.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' }); } return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function updateBreadcrumb(path) { if (!path || path === '/') { elements.breadcrumb.innerHTML = '根目录'; return; } const parts = path.split('/').filter(p => p); let html = '根目录'; let currentPath = ''; parts.forEach((part, index) => { currentPath += '/' + part; const isLast = index === parts.length - 1; html += `${escapeHtml(part)}`; }); elements.breadcrumb.innerHTML = html; } function updateButtonStates() { const count = selectedFiles.size; elements.btnDownload.disabled = count === 0; elements.btnDelete.disabled = count === 0; elements.btnMove.disabled = count !== 1; } function setupEventListeners() { elements.breadcrumb.addEventListener('click', e => { if (e.target.classList.contains('crumb')) { currentPath = e.target.dataset.path; loadFiles(currentPath); } }); elements.fileListBody.addEventListener('click', e => { const checkbox = e.target.closest('.file-checkbox'); const fileName = e.target.closest('.file-name'); const actionBtn = e.target.closest('.action-btn'); if (checkbox) { toggleFileSelection(checkbox.dataset.path, checkbox.checked); } else if (fileName) { const isDir = fileName.dataset.isDir === 'true'; const path = fileName.dataset.path; if (isDir) { currentPath = path; loadFiles(currentPath); } } else if (actionBtn) { const action = actionBtn.dataset.action; const path = actionBtn.dataset.path; switch (action) { case 'download': downloadFile(path); break; case 'preview': previewFile(path); break; case 'rename': showRenameModal(path); break; case 'delete': deleteFiles([path]); break; } } }); elements.selectAll.addEventListener('change', e => { const checkboxes = elements.fileListBody.querySelectorAll('.file-checkbox'); checkboxes.forEach(cb => { if (e.target.checked) { selectedFiles.add(cb.dataset.path); cb.checked = true; } else { selectedFiles.delete(cb.dataset.path); cb.checked = false; } }); updateButtonStates(); updateRowSelections(); }); elements.btnUpload.addEventListener('click', () => { elements.fileInput.click(); }); elements.btnDownload.addEventListener('click', () => { downloadSelected(); }); elements.btnDelete.addEventListener('click', () => { if (selectedFiles.size > 0) { if (confirm(`确定要删除选中的 ${selectedFiles.size} 个项目吗?`)) { deleteFiles(Array.from(selectedFiles)); } } }); elements.btnMove.addEventListener('click', () => { if (selectedFiles.size === 1) { showMoveModal(Array.from(selectedFiles)[0]); } }); elements.btnRefresh.addEventListener('click', () => { loadFiles(currentPath); }); elements.btnNewDir.addEventListener('click', () => { elements.newDirName.value = ''; elements.newDirModal.classList.add('active'); elements.newDirName.focus(); }); elements.fileInput.addEventListener('change', e => { uploadFiles(e.target.files); e.target.value = ''; }); elements.searchBtn.addEventListener('click', () => { searchFiles(); }); elements.searchInput.addEventListener('keypress', e => { if (e.key === 'Enter') searchFiles(); }); elements.searchInput.addEventListener('input', () => { if (elements.searchInput.value === '') { loadFiles(currentPath); } }); setupDragDrop(); setupModals(); } function searchFiles() { const keyword = elements.searchInput.value.trim(); if (!keyword) { loadFiles(currentPath); return; } elements.fileListBody.innerHTML = '搜索中...'; searchAllDirectories('/', keyword.toLowerCase(), []); } async function searchAllDirectories(path, keyword, results) { try { const response = await fetch(`${API_BASE}/files?path=${encodeURIComponent(path)}`); const data = await response.json(); const files = data.files || []; files.forEach(file => { if (file.name.toLowerCase().includes(keyword)) { results.push(file); } }); const dirs = files.filter(f => f.isDir); if (dirs.length > 0) { for (const dir of dirs) { await searchAllDirectories(dir.path, keyword, results); } } if (path === '/') { if (results.length > 0) { renderSearchResults(results, keyword); } else { elements.fileListBody.innerHTML = '🔍未找到匹配的文件'; } } } catch (error) { if (path === '/') { showNotification('搜索失败: ' + error.message, 'error'); elements.fileListBody.innerHTML = '搜索失败'; } } } function renderSearchResults(files, keyword) { const sortedFiles = files.sort((a, b) => a.name.localeCompare(b.name)); elements.fileListBody.innerHTML = sortedFiles.map(file => ` ${getFileIcon(file.name, file.isDir)} ${highlightKeyword(escapeHtml(file.name), keyword)} ${file.isDir ? '-' : formatFileSize(file.size)} ${formatTime(file.modTime)}
${file.isDir ? '' : ``} ${file.canPreview ? `` : ''}
`).join(''); updateButtonStates(); } function highlightKeyword(text, keyword) { if (!keyword) return text; const regex = new RegExp(`(${keyword})`, 'gi'); return text.replace(regex, '$1'); } function toggleFileSelection(path, selected) { if (selected) { selectedFiles.add(path); } else { selectedFiles.delete(path); } const checkbox = elements.fileListBody.querySelector(`.file-checkbox[data-path="${path}"]`); if (checkbox) { checkbox.checked = selected; } updateRowSelections(); updateButtonStates(); updateSelectAllState(); } function updateRowSelections() { const rows = elements.fileListBody.querySelectorAll('tr'); rows.forEach(row => { if (selectedFiles.has(row.dataset.path)) { row.classList.add('selected'); } else { row.classList.remove('selected'); } }); } function updateSelectAllState() { const checkboxes = elements.fileListBody.querySelectorAll('.file-checkbox'); const checkedCount = elements.fileListBody.querySelectorAll('.file-checkbox:checked').length; elements.selectAll.checked = checkedCount === checkboxes.length && checkboxes.length > 0; elements.selectAll.indeterminate = checkedCount > 0 && checkedCount < checkboxes.length; } function setupDragDrop() { document.addEventListener('dragenter', e => { e.preventDefault(); elements.dropZone.classList.add('active'); }); elements.dropZone.addEventListener('dragleave', e => { if (e.target === elements.dropZone) { elements.dropZone.classList.remove('active'); } }); elements.dropZone.addEventListener('dragover', e => { e.preventDefault(); }); elements.dropZone.addEventListener('drop', e => { e.preventDefault(); elements.dropZone.classList.remove('active'); uploadFiles(e.dataTransfer.files); }); } function setupModals() { elements.previewClose.addEventListener('click', () => { elements.previewModal.classList.remove('active'); }); elements.moveClose.addEventListener('click', () => { elements.moveModal.classList.remove('active'); }); elements.moveCancel.addEventListener('click', () => { elements.moveModal.classList.remove('active'); }); elements.moveConfirm.addEventListener('click', () => { moveFile(); }); elements.newDirClose.addEventListener('click', () => { elements.newDirModal.classList.remove('active'); }); elements.newDirCancel.addEventListener('click', () => { elements.newDirModal.classList.remove('active'); }); elements.newDirConfirm.addEventListener('click', () => { createDirectory(); }); elements.newDirName.addEventListener('keypress', e => { if (e.key === 'Enter') createDirectory(); }); elements.renameClose.addEventListener('click', () => { elements.renameModal.classList.remove('active'); }); elements.renameCancel.addEventListener('click', () => { elements.renameModal.classList.remove('active'); }); elements.renameConfirm.addEventListener('click', () => { renameFile(); }); elements.renameName.addEventListener('keypress', e => { if (e.key === 'Enter') renameFile(); }); elements.uploadClose.addEventListener('click', () => { if (!isUploading) { elements.uploadModal.classList.remove('active'); } }); elements.uploadCancel.addEventListener('click', () => { if (isUploading) { uploadXhrs.forEach(xhr => xhr.abort()); uploadXhrs = []; isUploading = false; elements.uploadModal.classList.remove('active'); } }); elements.uploadConfirm.addEventListener('click', () => { elements.uploadModal.classList.remove('active'); }); document.addEventListener('click', e => { if (e.target === elements.previewModal) { elements.previewModal.classList.remove('active'); } if (e.target === elements.moveModal) { elements.moveModal.classList.remove('active'); } if (e.target === elements.newDirModal) { elements.newDirModal.classList.remove('active'); } if (e.target === elements.renameModal) { elements.renameModal.classList.remove('active'); } if (e.target === elements.uploadModal && !isUploading) { elements.uploadModal.classList.remove('active'); } }); } function uploadFiles(files) { if (!files || files.length === 0) return; const fileArray = Array.from(files); elements.uploadList.innerHTML = fileArray.map((file, index) => `
${getFileIcon(file.name, false)}
${escapeHtml(file.name)}
等待中...
`).join(''); elements.uploadProgressContainer.classList.add('active'); elements.uploadProgressBar.style.width = '0%'; elements.uploadStats.classList.add('active'); elements.uploadCancel.style.display = 'block'; elements.uploadConfirm.style.display = 'none'; elements.uploadModal.classList.add('active'); let completed = 0; const total = fileArray.length; isUploading = true; uploadXhrs = []; fileArray.forEach((file, index) => { const formData = new FormData(); formData.append('file', file); const xhr = new XMLHttpRequest(); uploadXhrs.push(xhr); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); document.getElementById(`upload-status-${index}`).textContent = `上传中 ${percent}%`; } }); xhr.addEventListener('load', () => { completed++; const totalPercent = Math.round((completed / total) * 100); elements.uploadProgressBar.style.width = totalPercent + '%'; try { const response = JSON.parse(xhr.responseText); if (response.success) { document.getElementById(`upload-status-${index}`).textContent = '完成 ✓'; } else { document.getElementById(`upload-status-${index}`).textContent = '失败 ✗'; } } catch (e) { document.getElementById(`upload-status-${index}`).textContent = '失败 ✗'; } if (completed === total) { isUploading = false; elements.uploadCancel.style.display = 'none'; elements.uploadConfirm.style.display = 'block'; elements.uploadStats.textContent = `上传完成: 成功 ${completed} 个`; loadFiles(currentPath); } }); xhr.addEventListener('error', () => { completed++; document.getElementById(`upload-status-${index}`).textContent = '失败 ✗'; const totalPercent = Math.round((completed / total) * 100); elements.uploadProgressBar.style.width = totalPercent + '%'; if (completed === total) { isUploading = false; elements.uploadCancel.style.display = 'none'; elements.uploadConfirm.style.display = 'block'; elements.uploadStats.textContent = `上传完成: 成功 ${completed} 个`; loadFiles(currentPath); } }); xhr.open('POST', `${API_BASE}/upload?path=${encodeURIComponent(currentPath)}`); xhr.send(formData); }); } function downloadFile(path) { const url = `${API_BASE}/download?path=${encodeURIComponent(path)}`; const a = document.createElement('a'); a.href = url; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); } function downloadSelected() { const paths = Array.from(selectedFiles); if (paths.length === 1) { downloadFile(paths[0]); } else { const url = `${API_BASE}/download?paths=${encodeURIComponent(JSON.stringify(paths))}`; const a = document.createElement('a'); a.href = url; a.download = ''; document.body.appendChild(a); a.click(); document.body.removeChild(a); } } function deleteFiles(paths) { fetch(`${API_BASE}/files`, { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(paths) }) .then(response => response.json()) .then(data => { if (data.success) { showNotification(`已删除 ${paths.length} 个项目`, 'success'); selectedFiles.clear(); loadFiles(currentPath); } else { showNotification('删除失败', 'error'); } }) .catch(error => { showNotification('删除失败: ' + error.message, 'error'); }); } function showMoveModal(sourcePath) { elements.moveSource.value = sourcePath; elements.moveFilename.textContent = '移动: ' + sourcePath; loadDirectoryTree('/'); elements.moveModal.classList.add('active'); } function loadDirectoryTree(path) { fetch(`${API_BASE}/files?path=${encodeURIComponent(path)}`) .then(response => response.json()) .then(data => { renderDirectoryTree(data.files || [], path); updateMoveBreadcrumb(path); }); } function renderDirectoryTree(files, currentPath) { const dirs = (files || []).filter(f => f.isDir); if (dirs.length === 0) { elements.dirTree.innerHTML = '
📂

此目录为空

'; return; } elements.dirTree.innerHTML = dirs.map(dir => `
📁 ${escapeHtml(dir.name)}
`).join(''); elements.dirTree.querySelectorAll('.dir-item').forEach(item => { item.addEventListener('click', () => { loadDirectoryTree(item.dataset.path); }); }); const currentItem = elements.dirTree.querySelector(`.dir-item[data-path="${currentPath}"]`); if (currentItem) { currentItem.classList.add('current'); } } function updateMoveBreadcrumb(path) { if (!path || path === '/') { elements.moveBreadcrumb.innerHTML = '根目录'; return; } const parts = path.split('/').filter(p => p); let html = '根目录'; let currentPath = ''; parts.forEach(part => { currentPath += '/' + part; html += `${escapeHtml(part)}`; }); elements.moveBreadcrumb.innerHTML = html; elements.moveBreadcrumb.querySelectorAll('.crumb').forEach(crumb => { crumb.addEventListener('click', () => { loadDirectoryTree(crumb.dataset.path); }); }); } function moveFile() { const sourcePath = elements.moveSource.value; const destPath = currentPath; if (!sourcePath || !destPath) return; const sourceName = sourcePath.split('/').pop(); const newPath = destPath === '/' ? sourceName : destPath + '/' + sourceName; if (sourcePath === newPath) { showNotification('源路径和目标路径相同', 'error'); return; } fetch(`${API_BASE}/move`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ srcPath: sourcePath, destPath: newPath }) }) .then(response => response.json()) .then(data => { if (data.success) { showNotification('移动成功', 'success'); elements.moveModal.classList.remove('active'); selectedFiles.delete(sourcePath); loadFiles(currentPath); } else { showNotification('移动失败: ' + (data.error || '未知错误'), 'error'); } }) .catch(error => { showNotification('移动失败: ' + error.message, 'error'); }); } function showRenameModal(path) { const name = path.split('/').pop(); elements.renamePath.value = path; elements.renameName.value = name; elements.renameModal.classList.add('active'); elements.renameName.focus(); elements.renameName.select(); } function renameFile() { const path = elements.renamePath.value; const newName = elements.renameName.value.trim(); if (!path || !newName) { showNotification('请输入新名称', 'error'); return; } const dir = path.substring(0, path.lastIndexOf('/')); const newPath = dir ? dir + '/' + newName : newName; if (path === newPath) { elements.renameModal.classList.remove('active'); return; } fetch(`${API_BASE}/rename`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ oldPath: path, newName: newName }) }) .then(response => response.json()) .then(data => { if (data.success) { showNotification('重命名成功', 'success'); elements.renameModal.classList.remove('active'); selectedFiles.delete(path); if (selectedFiles.has(newPath)) { selectedFiles.delete(newPath); } loadFiles(currentPath); } else { showNotification('重命名失败: ' + (data.error || '未知错误'), 'error'); } }) .catch(error => { showNotification('重命名失败: ' + error.message, 'error'); }); } function createDirectory() { const name = elements.newDirName.value.trim(); if (!name) { showNotification('请输入文件夹名称', 'error'); return; } fetch(`${API_BASE}/dir`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ path: currentPath, name: name }) }) .then(response => response.json()) .then(data => { if (data.success) { showNotification('文件夹创建成功', 'success'); elements.newDirModal.classList.remove('active'); loadFiles(currentPath); } else { showNotification('创建失败: ' + (data.error || '未知错误'), 'error'); } }) .catch(error => { showNotification('创建失败: ' + error.message, 'error'); }); } function previewFile(path) { const name = path.split('/').pop(); elements.previewTitle.textContent = name; elements.previewBody.innerHTML = '
加载中...
'; elements.previewModal.classList.add('active'); fetch(`${API_BASE}/preview?path=${encodeURIComponent(path)}`) .then(response => { const contentType = response.headers.get('Content-Type'); if (contentType && contentType.startsWith('image/')) { return response.blob().then(blob => { return { type: 'image', data: URL.createObjectURL(blob) }; }); } else if (contentType && contentType.startsWith('text/')) { return response.text().then(text => { return { type: 'text', data: text }; }); } else if (contentType && contentType === 'application/pdf') { return response.blob().then(blob => { return { type: 'pdf', data: URL.createObjectURL(blob) }; }); } else { return { type: 'unsupported', message: '此文件类型不支持预览' }; } }) .then(result => { switch (result.type) { case 'image': elements.previewBody.innerHTML = `${escapeHtml(name)}`; break; case 'text': elements.previewBody.innerHTML = `
${escapeHtml(result.data)}
`; break; case 'pdf': elements.previewBody.innerHTML = ``; break; default: elements.previewBody.innerHTML = `
📄

${result.message || '此文件类型不支持预览'}

点击下载查看

`; } }) .catch(error => { elements.previewBody.innerHTML = `

预览失败: ${error.message}

`; }); } function startWatch() { if (eventSource) { eventSource.close(); eventSource = null; } try { eventSource = new EventSource(`${API_BASE}/watch`); eventSource.onopen = () => { elements.connectionStatus.textContent = '● 已连接'; elements.connectionStatus.className = 'connected'; }; eventSource.onerror = () => { if (eventSource.readyState === EventSource.CLOSED) return; elements.connectionStatus.textContent = '● 已连接'; elements.connectionStatus.className = 'connected'; }; eventSource.addEventListener('message', e => { try { const event = JSON.parse(e.data); handleWatchEvent(event); } catch (err) { } }); } catch (err) { elements.connectionStatus.textContent = '● 已连接'; elements.connectionStatus.className = 'connected'; } } function handleWatchEvent(event) { const inCurrentDir = currentPath === '/' || event.path.startsWith(currentPath + '/') || event.path === currentPath; if (!inCurrentDir) return; const pathParts = event.path.replace(rootDir, '').split('/').filter(p => p); const eventDir = '/' + pathParts.slice(0, -1).join('/'); if (eventDir !== currentPath) return; switch (event.type) { case 'create': showNotification(`新建文件: ${event.name}`, 'info'); loadFiles(currentPath); break; case 'delete': showNotification(`已删除: ${event.name}`, 'info'); selectedFiles.delete(event.path.replace(rootDir, '')); loadFiles(currentPath); break; case 'rename': case 'move': showNotification(`文件已移动: ${event.name}`, 'info'); loadFiles(currentPath); break; } } function showNotification(message, type = 'info') { elements.notification.textContent = message; elements.notification.className = 'notification ' + type; elements.notification.classList.add('show'); setTimeout(() => { elements.notification.classList.remove('show'); }, 3000); } function formatFileSize(bytes) { if (bytes === 0) return '-'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } const rootDir = ''; document.addEventListener('DOMContentLoaded', init); })();